contrek 1.1.5 → 1.1.6
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 +5 -0
- data/README.md +59 -0
- data/docs/images/strict_bounds_off.png +0 -0
- data/docs/images/strict_bounds_on.png +0 -0
- data/docs/images/stripes/whole.png +0 -0
- data/docs/images/stripes/whole_0.png +0 -0
- data/docs/images/stripes/whole_256.png +0 -0
- data/docs/images/stripes/whole_512.png +0 -0
- data/docs/images/stripes/whole_768.png +0 -0
- data/ext/cpp_polygon_finder/PolygonFinder/examples/example.cpp +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/images/graphs_1024x1024.png +0 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +113 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.h +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.cpp +100 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.h +4 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +7 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +16 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +2 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PointPool.cpp +19 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PointPool.h +24 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +30 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +39 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/RectBounds.h +12 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.cpp +1 -1
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +19 -5
- data/lib/contrek/bitmaps/chunky_bitmap.rb +50 -2
- data/lib/contrek/cpp/cpp_result.rb +42 -0
- data/lib/contrek/finder/concurrent/part.rb +9 -3
- data/lib/contrek/finder/concurrent/vertical_merger.rb +2 -2
- data/lib/contrek/finder/node_cluster.rb +15 -6
- data/lib/contrek/finder/polygon_finder.rb +1 -1
- data/lib/contrek/finder/result.rb +1 -5
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +1 -1
- metadata +13 -3
- data/lib/contrek/results/cpp_result.rb +0 -21
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1646e5fe0beaeafefcf2d60230a12ead391c9d75c771807332de7e06128198e4
|
|
4
|
+
data.tar.gz: a42d904e10ca321d8091263af986193332ac1507cdb2155e92373cf222faa864
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 32495e09d3c742d1c8a1011df2c08a204a510766a0bc4fa5bf9f647fcba5c15fe691fc5ef7207f1caeb9c9caa0ff041948cc12f6e042261dd1b1d49e69c8f430
|
|
7
|
+
data.tar.gz: 28a0e9ff0cc67cfbe7f56f73ae660d68a1ca82243ac656a67cff4c9aefff506eaf0230edc7973de9d900cb1f63e4946f9eed8c39cd9a04de01861dbcf04cb6cf
|
data/CHANGELOG.md
CHANGED
|
@@ -63,3 +63,8 @@ All notable changes to this project will be documented in this file.
|
|
|
63
63
|
### Changed
|
|
64
64
|
- **RawBitmap Integration (Ruby/C++):** Introduced a native buffer class for direct contour extraction, bypassing PNG encoding to significantly reduce latency and memory overhead.
|
|
65
65
|
- **Tiled Polygon Merger:** Added a spatial merger for `Finder` outputs to stitch polygons from discrete sub-areas (requiring only a 1px overlap). This multi-phase workflow supports coordinate translation and eliminates monolithic buffer requirements, optimizing the peak memory footprint.
|
|
66
|
+
|
|
67
|
+
## [1.1.6] - 2026-03-21
|
|
68
|
+
### Changed
|
|
69
|
+
- Added strict_bounds tracing mode: enables more accurate shape tracing by strictly adhering to pixel boundaries.
|
|
70
|
+
- Topological Consistency Fixes: improved the Topologically Consistent Merging algorithm to support progressive polygon tracing during sequential data streaming.
|
data/README.md
CHANGED
|
@@ -9,6 +9,45 @@ Contrek (**con**tour **trek**king) simply scans your png/raw bitmap and returns
|
|
|
9
9
|
In the following image all the non-white pixels have been examined and the result is the red polygon for the outer contour and the green one for the inner one
|
|
10
10
|

|
|
11
11
|
|
|
12
|
+
## 🚀 Why Contrek?
|
|
13
|
+
|
|
14
|
+
Contrek is a high-performance vectorization engine designed for massive datasets where memory efficiency and multi-core utilization are critical. Unlike traditional sequential contour tracers, Contrek is built on a **Stripe-Merging Architecture** that allows it to process images in independent segments and reconstruct the resulting polygons with perfect topological integrity.
|
|
15
|
+
|
|
16
|
+
### 1. Parallel Processing Power
|
|
17
|
+
Contrek is designed to saturate all available CPU cores. By partitioning a single image into stripes, it can assign each segment to a different thread.
|
|
18
|
+
* **The Result:** On multi-core systems, Contrek achieves unmatched extraction speeds by parallelizing the workload.
|
|
19
|
+
* **Scalability:** Performance scales directly with your hardware, making it the ideal choice for high-throughput processing servers.
|
|
20
|
+
|
|
21
|
+
### 2. Streamed Memory Management
|
|
22
|
+
Standard vectorization requires loading entire high-resolution images into RAM, which often leads to memory saturation.
|
|
23
|
+
* **The Solution:** Contrek enables **streamed processing** (see the C++ example in the repo). You can define a specific stripe height (buffer) and process the image incrementally, consuming only the memory allocated for that buffer.
|
|
24
|
+
* **The Benefit:** Vectorize massive, gigapixel-scale images or raw datasets directly from memory buffers. This allows for high-performance processing even on systems with limited memory that would otherwise fail.
|
|
25
|
+
|
|
26
|
+
### 3. Intelligent "Stitching" Technology
|
|
27
|
+
The core strength of Contrek is its **Topologically Consistent Merging** algorithm, which maintains a deep understanding of polygon connectivity across stripe boundaries.
|
|
28
|
+
* Whether you are optimizing for **speed** (parallel cores) or **memory** (sequential streaming), the output is always a seamless, continuous set of vector data, free from gaps or duplicates at the seams.
|
|
29
|
+
|
|
30
|
+
<table>
|
|
31
|
+
<tr>
|
|
32
|
+
<td><img src="docs/images/stripes/whole_0.png" alt="Contour tracing stripe 0" width="50%"></td>
|
|
33
|
+
<td rowspan="4" align="center">
|
|
34
|
+
<img src="docs/images/stripes/whole.png" alt="Contour tracing stripe 0" width="50%">
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
<tr>
|
|
38
|
+
<td><img src="docs/images/stripes/whole_256.png" alt="Contour tracing stripe 0" width="50%"></td>
|
|
39
|
+
</tr>
|
|
40
|
+
<tr>
|
|
41
|
+
<td><img src="docs/images/stripes/whole_512.png" alt="Contour tracing stripe 0" width="50%"></td>
|
|
42
|
+
</tr>
|
|
43
|
+
<tr>
|
|
44
|
+
<td><img src="docs/images/stripes/whole_768.png" alt="Contour tracing stripe 0" width="50%"></td>
|
|
45
|
+
</tr>
|
|
46
|
+
<tr><td colspan="2"><em><b>Left:</b> The image is split into 4 independent memory buffers (stripes).
|
|
47
|
+
<br><b>Right:</b> Contrek ensures <b>perfect topological continuity</b> during merging.</em>
|
|
48
|
+
<br><span style="color: #d32f2f;">■</span> <b>Red:</b> Outer contours |
|
|
49
|
+
<span style="color: #2e7d32;">■</span> <b>Dark Green:</b> Inner zones</td></tr>
|
|
50
|
+
</table>
|
|
12
51
|
|
|
13
52
|
## Prerequisites
|
|
14
53
|
|
|
@@ -180,6 +219,26 @@ result = Contrek.contour!(
|
|
|
180
219
|
)
|
|
181
220
|
```
|
|
182
221
|
|
|
222
|
+
## Tracing Modes
|
|
223
|
+
|
|
224
|
+
Contrek provides two levels of precision for polygon generation to balance data density and topological accuracy.
|
|
225
|
+
|
|
226
|
+
### **Standard Mode** (Default)
|
|
227
|
+
Optimized for **speed** and **lightweight** output.
|
|
228
|
+
* **Behavior:** Traces outer boundaries while simplifying collinear segments.
|
|
229
|
+
* **Result:** Fewer vertices and smaller memory footprint.
|
|
230
|
+
|
|
231
|
+
### **Strict Mode** (`strict_bounds: true`)
|
|
232
|
+
Engineered for **Pixel-Perfect** precision.
|
|
233
|
+
* **Behavior:** Preserves every junction and internal connectivity change.
|
|
234
|
+
* **Result:** 100% topologically faithful geometry with no micro-gaps between adjacent polygons.
|
|
235
|
+
|
|
236
|
+
Below are two images illustrating the difference in tracing modes. In the first case, with **strict_bounds ON**, the anti-clockwise sequence includes two additional points, **H** and **I**, which trace the shape more accurately. In the second case, the transition between **G** and **H** is approximated, omitting the indentation.
|
|
237
|
+
| Strict Bounds ON | Strict Bounds OFF |
|
|
238
|
+
|:---:|:---:|
|
|
239
|
+
|  |  |
|
|
240
|
+
|
|
241
|
+
|
|
183
242
|
## Result
|
|
184
243
|
|
|
185
244
|
The result structure contains polygon coordinates and a set of metadata. Polygon coordinates can be accessed via:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -14,12 +14,16 @@
|
|
|
14
14
|
#include <map>
|
|
15
15
|
#include <vector>
|
|
16
16
|
#include <string>
|
|
17
|
+
#include <cstring>
|
|
18
|
+
#include <algorithm>
|
|
19
|
+
#include <cstdio>
|
|
17
20
|
#include "polygon/finder/PolygonFinder.h"
|
|
18
21
|
#include "polygon/finder/concurrent/ClippedPolygonFinder.h"
|
|
19
22
|
#include "polygon/bitmaps/Bitmap.h"
|
|
20
23
|
#include "polygon/bitmaps/FastPngBitmap.h"
|
|
21
24
|
#include "polygon/bitmaps/RawBitmap.h"
|
|
22
25
|
#include "polygon/bitmaps/RemoteFastPngBitmap.h"
|
|
26
|
+
#include "polygon/bitmaps/spng.h"
|
|
23
27
|
#include "polygon/matchers/Matcher.h"
|
|
24
28
|
#include "polygon/matchers/RGBMatcher.h"
|
|
25
29
|
#include "polygon/matchers/RGBNotMatcher.h"
|
|
@@ -27,6 +31,7 @@
|
|
|
27
31
|
#include "polygon/finder/optionparser.h"
|
|
28
32
|
#include "polygon/finder/concurrent/Finder.h"
|
|
29
33
|
#include "polygon/finder/concurrent/HorizontalMerger.h"
|
|
34
|
+
#include "polygon/finder/concurrent/VerticalMerger.h"
|
|
30
35
|
#include "polygon/finder/concurrent/Sequence.h"
|
|
31
36
|
#include "polygon/finder/concurrent/Position.h"
|
|
32
37
|
#include "polygon/finder/Polygon.h"
|
|
@@ -46,6 +51,7 @@ void Tests::test_a()
|
|
|
46
51
|
Bitmap b(chunk, 16);
|
|
47
52
|
PolygonFinder pl(&b, &matcher, nullptr, &arguments);
|
|
48
53
|
ProcessResult *o = pl.process_info();
|
|
54
|
+
// o->print_polygons();
|
|
49
55
|
std::vector<int> outer_array{11, 1, 6, 2, 6, 3, 6, 4, 6, 5, 11, 5, 11, 4, 11, 3, 11, 2, 11, 1};
|
|
50
56
|
std::vector<int> inner_array{7, 3, 10, 3, 10, 4, 7, 4};
|
|
51
57
|
std::vector<int> array_compare;
|
|
@@ -253,3 +259,110 @@ void Tests::test_h()
|
|
|
253
259
|
delete left_result;
|
|
254
260
|
delete right_result;
|
|
255
261
|
}
|
|
262
|
+
|
|
263
|
+
/* In this example, PNG data is read by streaming into a user-defined buffer height.
|
|
264
|
+
Contrek scans each stripe and extracts the polygons. Finally, it merges all
|
|
265
|
+
polygons from every stripe as if they had been read from a single image and saves
|
|
266
|
+
the result polygons on a png image.
|
|
267
|
+
This approach allows for processing large PNG files on systems where memory
|
|
268
|
+
would otherwise be insufficient.
|
|
269
|
+
*/
|
|
270
|
+
void stream_png_image(const std::string& filepath, uint32_t stripe_height) {
|
|
271
|
+
std::vector<ProcessResult*> result_clones;
|
|
272
|
+
std::vector<std::string> varguments = {};
|
|
273
|
+
VerticalMerger vmerger(0, &varguments);
|
|
274
|
+
|
|
275
|
+
// opens image to stream
|
|
276
|
+
FILE* fp = fopen(filepath.c_str(), "rb");
|
|
277
|
+
if (!fp) {
|
|
278
|
+
std::cerr << "Unable open file: " << filepath << std::endl;
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// exams image
|
|
283
|
+
spng_ctx *ctx = spng_ctx_new(0);
|
|
284
|
+
spng_set_png_file(ctx, fp);
|
|
285
|
+
struct spng_ihdr ihdr;
|
|
286
|
+
if (spng_get_ihdr(ctx, &ihdr)) {
|
|
287
|
+
fclose(fp);
|
|
288
|
+
spng_ctx_free(ctx);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
uint32_t total_width = ihdr.width;
|
|
292
|
+
uint32_t total_height = ihdr.height;
|
|
293
|
+
|
|
294
|
+
// allocates stripe buffer
|
|
295
|
+
RawBitmap stripe_bitmap;
|
|
296
|
+
stripe_bitmap.define(total_width, stripe_height, 4, true);
|
|
297
|
+
RGBNotMatcher not_matcher(-1);
|
|
298
|
+
|
|
299
|
+
if (spng_decode_image(ctx, NULL, 0, SPNG_FMT_RGBA8, SPNG_DECODE_PROGRESSIVE)) {
|
|
300
|
+
fclose(fp);
|
|
301
|
+
spng_ctx_free(ctx);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
size_t row_size = static_cast<size_t>(total_width) * 4;
|
|
306
|
+
// main strpes loop
|
|
307
|
+
for (uint32_t current_y_offset = 0; current_y_offset < total_height; current_y_offset += stripe_height) {
|
|
308
|
+
int uncovered_height = total_height - current_y_offset;
|
|
309
|
+
|
|
310
|
+
// copy previous last line to the next new one (each contigue stripe must share one pixel scanline)
|
|
311
|
+
if (current_y_offset > 0) {
|
|
312
|
+
unsigned char* last_row_prev = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(stripe_height - 1));
|
|
313
|
+
unsigned char* first_row_curr = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(0));
|
|
314
|
+
std::memcpy(first_row_curr, last_row_prev, row_size);
|
|
315
|
+
}
|
|
316
|
+
// clears the part of the stripe wont be overwritten by png data
|
|
317
|
+
if (uncovered_height < stripe_height)
|
|
318
|
+
{ stripe_bitmap.draw_filled_rectangle(0, 1, total_width, stripe_height - 1, 255, 255, 255);
|
|
319
|
+
}
|
|
320
|
+
// decoding data directly in the stripe buffer
|
|
321
|
+
uint32_t lines_to_read = std::min(stripe_height, total_height - current_y_offset);
|
|
322
|
+
for (uint32_t y = (current_y_offset == 0 ? 0 : 1); y < lines_to_read; y++) {
|
|
323
|
+
unsigned char* row_ptr = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(y));
|
|
324
|
+
int ret = spng_decode_row(ctx, row_ptr, row_size);
|
|
325
|
+
if (ret != 0 && ret != SPNG_EOI) break;
|
|
326
|
+
}
|
|
327
|
+
// stripe contour tracing
|
|
328
|
+
std::vector<std::string> finder_arguments = {
|
|
329
|
+
"--versus=a",
|
|
330
|
+
"--strict_bounds" // <- this option is strictly needed when working with vertical merger
|
|
331
|
+
};
|
|
332
|
+
PolygonFinder polygon_finder(&stripe_bitmap, ¬_matcher, nullptr, &finder_arguments);
|
|
333
|
+
ProcessResult *result = polygon_finder.process_info();
|
|
334
|
+
if (result) {
|
|
335
|
+
std::cout << "Founds polygons: " << result->groups << std::endl;
|
|
336
|
+
ProcessResult* safe_result = result->clone();
|
|
337
|
+
result_clones.push_back(safe_result);
|
|
338
|
+
vmerger.add_tile(*safe_result);
|
|
339
|
+
delete result;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
std::cout << "Merging polygons ..." << std::endl;
|
|
344
|
+
ProcessResult *merged_result = vmerger.process_info();
|
|
345
|
+
std::cout << "Founds total polygons: " << merged_result->groups << std::endl;
|
|
346
|
+
|
|
347
|
+
if (merged_result) {
|
|
348
|
+
RawBitmap full_bitmap;
|
|
349
|
+
full_bitmap.define(total_width, total_height, 4, true);
|
|
350
|
+
full_bitmap.fill(255, 255, 255);
|
|
351
|
+
merged_result->draw_on_bitmap(full_bitmap);
|
|
352
|
+
std::cout << "Saving whole png ..." << std::endl;
|
|
353
|
+
if (full_bitmap.save_to_png("whole.png")) {
|
|
354
|
+
std::cout << "Png saved!" << std::endl;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
delete merged_result;
|
|
358
|
+
// frees memory
|
|
359
|
+
for (auto c : result_clones) {
|
|
360
|
+
delete c;
|
|
361
|
+
}
|
|
362
|
+
spng_ctx_free(ctx);
|
|
363
|
+
fclose(fp);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
void Tests::test_i() {
|
|
367
|
+
stream_png_image("../images/graphs_1024x1024.png", 300);
|
|
368
|
+
}
|
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
#include <memory>
|
|
11
|
+
#include <cstdio>
|
|
12
|
+
#include <iostream>
|
|
13
|
+
#include <cstring>
|
|
14
|
+
#include <cerrno>
|
|
15
|
+
#include <string>
|
|
16
|
+
#include "spng.h"
|
|
11
17
|
#include "RawBitmap.h"
|
|
12
18
|
|
|
13
19
|
RawBitmap::RawBitmap() : Bitmap("", 0),
|
|
@@ -42,7 +48,7 @@ unsigned int RawBitmap::rgb_value_at(int x, int y) {
|
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
void RawBitmap::draw_pixel(int x, int y, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
45
|
-
if (x >= width || y >= height) return;
|
|
51
|
+
if (x < 0 || x >= width || y < 0 || y >= height) return;
|
|
46
52
|
unsigned char* p = &image[(static_cast<size_t>(y) * width + x) * bpp];
|
|
47
53
|
p[0] = r;
|
|
48
54
|
if (bpp > 1) p[1] = g;
|
|
@@ -62,3 +68,96 @@ uint32_t RawBitmap::define(uint width, uint height, int bytes_per_pixel, bool cl
|
|
|
62
68
|
return dimension;
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
void RawBitmap::fill(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
72
|
+
if (!image) return;
|
|
73
|
+
size_t size = static_cast<size_t>(width) * height * bpp;
|
|
74
|
+
|
|
75
|
+
if (r == g && g == b && b == a) {
|
|
76
|
+
std::memset(image.get(), r, size);
|
|
77
|
+
} else {
|
|
78
|
+
for (size_t i = 0; i < size; i += bpp) {
|
|
79
|
+
image[i] = r;
|
|
80
|
+
if (bpp > 1) image[i+1] = g;
|
|
81
|
+
if (bpp > 2) image[i+2] = b;
|
|
82
|
+
if (bpp > 3) image[i+3] = a;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
void RawBitmap::draw_line(int x0, int y0, int x1, int y1, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
88
|
+
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
|
|
89
|
+
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
|
|
90
|
+
int err = dx + dy, e2;
|
|
91
|
+
while (true) {
|
|
92
|
+
draw_pixel(x0, y0, r, g, b, a);
|
|
93
|
+
if (x0 == x1 && y0 == y1) break;
|
|
94
|
+
e2 = 2 * err;
|
|
95
|
+
if (e2 >= dy) {
|
|
96
|
+
err += dy; x0 += sx;
|
|
97
|
+
}
|
|
98
|
+
if (e2 <= dx) {
|
|
99
|
+
err += dx; y0 += sy;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
void RawBitmap::draw_filled_rectangle(int x, int y, int w, int h, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
105
|
+
if (w <= 0 || h <= 0) return;
|
|
106
|
+
|
|
107
|
+
for (int i = 0; i < h; ++i) {
|
|
108
|
+
int current_y = y + i;
|
|
109
|
+
this->draw_line(x, current_y, x + w - 1, current_y, r, g, b, a);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
bool RawBitmap::save_to_png(const std::string& filename) {
|
|
114
|
+
if (this->width == 0 || this->height == 0 || !this->image) {
|
|
115
|
+
std::cerr << "Error: Bitmap empty or uninitialized" << std::endl;
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
FILE *fp = fopen(filename.c_str(), "wb");
|
|
119
|
+
if (!fp) {
|
|
120
|
+
std::cerr << "Unable open file" << filename << ": "
|
|
121
|
+
<< std::strerror(errno) << std::endl;
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
spng_ctx *ctx = spng_ctx_new(SPNG_CTX_ENCODER);
|
|
125
|
+
if (!ctx) {
|
|
126
|
+
fclose(fp);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
spng_set_png_file(ctx, fp);
|
|
130
|
+
struct spng_ihdr ihdr;
|
|
131
|
+
std::memset(&ihdr, 0, sizeof(struct spng_ihdr));
|
|
132
|
+
|
|
133
|
+
ihdr.width = this->width;
|
|
134
|
+
ihdr.height = this->height;
|
|
135
|
+
ihdr.bit_depth = 8;
|
|
136
|
+
|
|
137
|
+
// color type by bpp
|
|
138
|
+
// 6 = RGBA (Truecolor with Alpha)
|
|
139
|
+
// 2 = RGB (Truecolor)
|
|
140
|
+
ihdr.color_type = (this->bpp == 4) ? 6 : 2;
|
|
141
|
+
|
|
142
|
+
int ret = spng_set_ihdr(ctx, &ihdr);
|
|
143
|
+
if (ret) {
|
|
144
|
+
std::cerr << "Error spng_set_ihdr: " << spng_strerror(ret) << std::endl;
|
|
145
|
+
spng_ctx_free(ctx);
|
|
146
|
+
fclose(fp);
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// encoding
|
|
151
|
+
size_t image_size = static_cast<size_t>(this->width) * this->height * this->bpp;
|
|
152
|
+
ret = spng_encode_image(ctx, this->image.get(), image_size, SPNG_FMT_PNG, SPNG_ENCODE_FINALIZE);
|
|
153
|
+
if (ret) {
|
|
154
|
+
std::cerr << "Encoding error: " << spng_strerror(ret) << " (code: " << ret << ")" << std::endl;
|
|
155
|
+
std::cerr << "Data W: " << width << " H: " << height << " BPP: " << bpp << " Size: " << image_size << std::endl;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
spng_ctx_free(ctx);
|
|
159
|
+
fflush(fp);
|
|
160
|
+
fclose(fp);
|
|
161
|
+
|
|
162
|
+
return (ret == 0);
|
|
163
|
+
}
|
|
@@ -27,6 +27,10 @@ class RawBitmap : public Bitmap {
|
|
|
27
27
|
int get_bytes_per_pixel() const;
|
|
28
28
|
uint32_t define(uint width, uint height, int bytes_per_pixel, bool clear = false);
|
|
29
29
|
void draw_pixel(int x, int y, unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255);
|
|
30
|
+
bool save_to_png(const std::string& filename);
|
|
31
|
+
void fill(unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255);
|
|
32
|
+
void draw_line(int x0, int y0, int x1, int y1, unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255);
|
|
33
|
+
void draw_filled_rectangle(int x, int y, int w, int h, unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255);
|
|
30
34
|
|
|
31
35
|
protected:
|
|
32
36
|
std::unique_ptr<unsigned char[]> image;
|
|
@@ -22,7 +22,9 @@ void FinderUtils::sanitize_options(pf_Options& options, std::vector<std::string>
|
|
|
22
22
|
char** argv = &argv0[0];
|
|
23
23
|
int argc = argv0.size() -1;
|
|
24
24
|
|
|
25
|
-
enum optionIndex { COMPRESS_UNIQ, VERSUS, COMPRESS_VISVALINGAM, COMPRESS_LINEAR, NUMBER_OF_TILES,
|
|
25
|
+
enum optionIndex { COMPRESS_UNIQ, VERSUS, COMPRESS_VISVALINGAM, COMPRESS_LINEAR, NUMBER_OF_TILES,
|
|
26
|
+
COMPRESS_VISVALINGAM_TOLERANCE, TREEMAP, NAMED_SEQUENCES, BOUNDS, CONNECTIVITY,
|
|
27
|
+
STRICT_BOUNDS };
|
|
26
28
|
const option::Descriptor usage[] = {
|
|
27
29
|
// {UNKNOWN, 0,"" , "" ,option::Arg::None, 0},
|
|
28
30
|
{COMPRESS_VISVALINGAM, 0, "" , "compress_visvalingam", option::Arg::None, 0},
|
|
@@ -33,6 +35,7 @@ void FinderUtils::sanitize_options(pf_Options& options, std::vector<std::string>
|
|
|
33
35
|
{TREEMAP, 0, "", "treemap", option::Arg::None, 0},
|
|
34
36
|
{NAMED_SEQUENCES, 0, "", "named_sequences", option::Arg::None, 0},
|
|
35
37
|
{BOUNDS, 0, "", "bounds", option::Arg::None, 0},
|
|
38
|
+
{STRICT_BOUNDS, 0, "", "strict_bounds", option::Arg::None, 0},
|
|
36
39
|
{VERSUS, 0, "v", "versus", option::Arg::Optional, 0},
|
|
37
40
|
{CONNECTIVITY, 0, "c", "connectivity", option::Arg::Optional, 0},
|
|
38
41
|
{0, 0, 0, 0, 0, 0}
|
|
@@ -86,6 +89,9 @@ void FinderUtils::sanitize_options(pf_Options& options, std::vector<std::string>
|
|
|
86
89
|
if (ioptions[BOUNDS].count() > 0)
|
|
87
90
|
{ options.bounds = true;
|
|
88
91
|
}
|
|
92
|
+
if (ioptions[STRICT_BOUNDS].count() > 0)
|
|
93
|
+
{ options.strict_bounds = true;
|
|
94
|
+
}
|
|
89
95
|
// COMPRESS LINEAR
|
|
90
96
|
if (ioptions[COMPRESS_LINEAR].count() > 0)
|
|
91
97
|
{ options.compress_linear = true;
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
#include "NodeCluster.h"
|
|
19
19
|
#include "Node.h"
|
|
20
20
|
#include "RectBounds.h"
|
|
21
|
+
#include "PointPool.h"
|
|
21
22
|
#include "../reducers/UniqReducer.h"
|
|
22
23
|
#include "../reducers/LinearReducer.h"
|
|
23
24
|
#include "../reducers/VisvalingamReducer.h"
|
|
@@ -243,7 +244,7 @@ std::pair<int, int> NodeCluster::test_in_hole_o(Node* node)
|
|
|
243
244
|
|
|
244
245
|
void NodeCluster::plot_inner_node(std::vector<Point*>& sequence_coords, Node *node, int versus, Node *stop_at, Node *start_node) {
|
|
245
246
|
Node *current_node = node;
|
|
246
|
-
|
|
247
|
+
bool strict_bounds = this->options->strict_bounds;
|
|
247
248
|
while (current_node != nullptr) {
|
|
248
249
|
current_node->outer_index = start_node->outer_index;
|
|
249
250
|
current_node->inner_index = stop_at->inner_index;
|
|
@@ -271,7 +272,12 @@ void NodeCluster::plot_inner_node(std::vector<Point*>& sequence_coords, Node *no
|
|
|
271
272
|
sequence_coords.push_back(next_node->coords_entering_to(current_node, versus, Node::INNER));
|
|
272
273
|
}
|
|
273
274
|
}
|
|
274
|
-
}
|
|
275
|
+
} else if (strict_bounds) {
|
|
276
|
+
bool first_is_max = ((current_node->y > last_node->y) == (versus == Node::A));
|
|
277
|
+
sequence_coords.push_back(this->points_pool.acquire((first_is_max ? last_node->max_x : last_node->min_x), current_node->y));
|
|
278
|
+
sequence_coords.push_back(this->points_pool.acquire((first_is_max ? next_node->min_x : next_node->max_x), current_node->y));
|
|
279
|
+
}
|
|
280
|
+
|
|
275
281
|
if (current_node->track_uncomplete()) {
|
|
276
282
|
this->inner_new->push_back(current_node);
|
|
277
283
|
} else {
|
|
@@ -284,6 +290,7 @@ void NodeCluster::plot_inner_node(std::vector<Point*>& sequence_coords, Node *no
|
|
|
284
290
|
|
|
285
291
|
void NodeCluster::plot_node(std::vector<Point*>& sequence_coords, Node *node, Node *start_node, int versus, RectBounds& bounds) {
|
|
286
292
|
Node *current_node = node;
|
|
293
|
+
bool strict_bounds = this->options->strict_bounds;
|
|
287
294
|
|
|
288
295
|
while (current_node != nullptr) {
|
|
289
296
|
root_nodes->remove(current_node);
|
|
@@ -315,7 +322,13 @@ void NodeCluster::plot_node(std::vector<Point*>& sequence_coords, Node *node, No
|
|
|
315
322
|
inner_plot->contains(current_node) ? inner_plot->remove(current_node) : inner_plot->push_back(current_node);
|
|
316
323
|
}
|
|
317
324
|
}
|
|
318
|
-
}
|
|
325
|
+
} else if (strict_bounds) {
|
|
326
|
+
bool is_down = current_node->y > last_node->y;
|
|
327
|
+
bool is_a = (versus == Node::A);
|
|
328
|
+
sequence_coords.push_back(this->points_pool.acquire((is_down == is_a ? last_node->min_x : last_node->max_x), current_node->y));
|
|
329
|
+
sequence_coords.push_back(this->points_pool.acquire((is_down == is_a ? next_node->max_x : next_node->min_x), current_node->y));
|
|
330
|
+
}
|
|
331
|
+
|
|
319
332
|
if (current_node == start_node) {
|
|
320
333
|
if (current_node->track_complete()) break;
|
|
321
334
|
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
#include "Lists.h"
|
|
19
19
|
#include "RectBounds.h"
|
|
20
20
|
#include "Polygon.h"
|
|
21
|
+
#include "PointPool.h"
|
|
21
22
|
|
|
22
23
|
class Node;
|
|
23
24
|
struct Point;
|
|
@@ -34,6 +35,7 @@ class NodeCluster {
|
|
|
34
35
|
int count = 0;
|
|
35
36
|
int nodes;
|
|
36
37
|
int width;
|
|
38
|
+
PointPool points_pool;
|
|
37
39
|
|
|
38
40
|
public:
|
|
39
41
|
pf_Options *options;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PointPool.cpp
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2025-2026 Emanuele Cesaroni
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the GNU Affero General Public License v3 (AGPLv3).
|
|
7
|
+
* See the LICENSE file in this directory for the full license text.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "PointPool.h"
|
|
11
|
+
|
|
12
|
+
Point* PointPool::acquire(int x, int y) {
|
|
13
|
+
storage.emplace_back(x, y);
|
|
14
|
+
return &storage.back();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
void PointPool::clear() {
|
|
18
|
+
storage.clear();
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PointPool.h
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2025-2026 Emanuele Cesaroni
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the GNU Affero General Public License v3 (AGPLv3).
|
|
7
|
+
* See the LICENSE file in this directory for the full license text.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#pragma once
|
|
11
|
+
#include <deque>
|
|
12
|
+
#include <cstdint>
|
|
13
|
+
#include <string>
|
|
14
|
+
#include "Node.h"
|
|
15
|
+
|
|
16
|
+
class PointPool {
|
|
17
|
+
private:
|
|
18
|
+
std::deque<Point> storage;
|
|
19
|
+
|
|
20
|
+
public:
|
|
21
|
+
Point* acquire(int x, int y);
|
|
22
|
+
void clear();
|
|
23
|
+
size_t size() const { return storage.size(); }
|
|
24
|
+
};
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
#include <utility>
|
|
18
18
|
#include "PolygonFinder.h"
|
|
19
19
|
#include "../bitmaps/Bitmap.h"
|
|
20
|
+
#include "../bitmaps/RawBitmap.h"
|
|
21
|
+
|
|
20
22
|
#include "../matchers/Matcher.h"
|
|
21
23
|
#include "../matchers/RGBMatcher.h"
|
|
22
24
|
#include "../matchers/RGBNotMatcher.h"
|
|
@@ -124,3 +126,31 @@ ProcessResult* PolygonFinder::process_info() {
|
|
|
124
126
|
else pr->named_sequence = "";
|
|
125
127
|
return(pr);
|
|
126
128
|
}
|
|
129
|
+
|
|
130
|
+
void ProcessResult::draw_on_bitmap(RawBitmap& bitmap) const {
|
|
131
|
+
for (const auto& poly : polygons) {
|
|
132
|
+
// --- OUTER ---
|
|
133
|
+
if (!poly.outer.empty()) {
|
|
134
|
+
for (size_t i = 0; i < poly.outer.size() - 1; ++i) {
|
|
135
|
+
Point* p1 = poly.outer[i];
|
|
136
|
+
Point* p2 = poly.outer[i+1];
|
|
137
|
+
bitmap.draw_line(p1->x, p1->y, p2->x, p2->y, 255, 0, 0, 255);
|
|
138
|
+
}
|
|
139
|
+
Point* last = poly.outer.back();
|
|
140
|
+
Point* first = poly.outer.front();
|
|
141
|
+
bitmap.draw_line(last->x, last->y, first->x, first->y, 255, 0, 0, 255);
|
|
142
|
+
}
|
|
143
|
+
// --- INNER ---
|
|
144
|
+
for (const auto& sequence : poly.inner) {
|
|
145
|
+
if (sequence.empty()) continue;
|
|
146
|
+
for (size_t i = 0; i < sequence.size() - 1; ++i) {
|
|
147
|
+
Point* p1 = sequence[i];
|
|
148
|
+
Point* p2 = sequence[i+1];
|
|
149
|
+
bitmap.draw_line(p1->x, p1->y, p2->x, p2->y, 0, 128, 0, 255);
|
|
150
|
+
}
|
|
151
|
+
Point* last = sequence.back();
|
|
152
|
+
Point* first = sequence.front();
|
|
153
|
+
bitmap.draw_line(last->x, last->y, first->x, first->y, 0, 128, 0, 255);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -27,6 +27,7 @@ class Matcher;
|
|
|
27
27
|
class RGBMatcher;
|
|
28
28
|
class NodeCluster;
|
|
29
29
|
class Node;
|
|
30
|
+
class RawBitmap;
|
|
30
31
|
struct Point;
|
|
31
32
|
|
|
32
33
|
struct ShapeLine {
|
|
@@ -41,6 +42,7 @@ struct pf_Options {
|
|
|
41
42
|
bool compress_visvalingam = false;
|
|
42
43
|
bool named_sequences = false;
|
|
43
44
|
bool bounds = false;
|
|
45
|
+
bool strict_bounds = false;
|
|
44
46
|
int connectivity_offset = 0;
|
|
45
47
|
float compress_visvalingam_tolerance = 10.0;
|
|
46
48
|
int number_of_tiles = 1;
|
|
@@ -48,6 +50,7 @@ struct pf_Options {
|
|
|
48
50
|
return(versus == Node::A ? "a" : "o");
|
|
49
51
|
}
|
|
50
52
|
};
|
|
53
|
+
|
|
51
54
|
struct ProcessResult {
|
|
52
55
|
int groups;
|
|
53
56
|
int width, height;
|
|
@@ -55,6 +58,7 @@ struct ProcessResult {
|
|
|
55
58
|
std::list<Polygon> polygons;
|
|
56
59
|
std::string named_sequence;
|
|
57
60
|
std::vector<std::pair<int, int>> treemap;
|
|
61
|
+
void draw_on_bitmap(RawBitmap& canvas) const;
|
|
58
62
|
|
|
59
63
|
void print_polygons() {
|
|
60
64
|
int counter = 0;
|
|
@@ -67,7 +71,7 @@ struct ProcessResult {
|
|
|
67
71
|
first = false;
|
|
68
72
|
for (const Point* p : seq) std::cout << p->toString();
|
|
69
73
|
}
|
|
70
|
-
std::cout << "\n";
|
|
74
|
+
std::cout << "\n" << polygon.bounds.to_string() <<"\n";
|
|
71
75
|
counter++;
|
|
72
76
|
}
|
|
73
77
|
}
|
|
@@ -89,6 +93,40 @@ struct ProcessResult {
|
|
|
89
93
|
polygon.bounds.max_x += x;
|
|
90
94
|
}
|
|
91
95
|
}
|
|
96
|
+
|
|
97
|
+
ProcessResult* clone() const {
|
|
98
|
+
ProcessResult* new_res = new ProcessResult();
|
|
99
|
+
new_res->groups = this->groups;
|
|
100
|
+
new_res->width = this->width;
|
|
101
|
+
new_res->height = this->height;
|
|
102
|
+
new_res->benchmarks = this->benchmarks;
|
|
103
|
+
new_res->named_sequence = this->named_sequence;
|
|
104
|
+
new_res->treemap = this->treemap;
|
|
105
|
+
|
|
106
|
+
for (const auto& poly : this->polygons) {
|
|
107
|
+
Polygon new_poly;
|
|
108
|
+
// Bounds
|
|
109
|
+
new_poly.bounds = poly.bounds;
|
|
110
|
+
// outer
|
|
111
|
+
for (const Point* p : poly.outer) {
|
|
112
|
+
if (p) {
|
|
113
|
+
new_poly.outer.push_back(new Point(p->x, p->y));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// inner
|
|
117
|
+
for (const auto& seq : poly.inner) {
|
|
118
|
+
std::vector<Point*> new_seq;
|
|
119
|
+
for (const Point* p : seq) {
|
|
120
|
+
if (p) {
|
|
121
|
+
new_seq.push_back(new Point(p->x, p->y));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
new_poly.inner.push_back(new_seq);
|
|
125
|
+
}
|
|
126
|
+
new_res->polygons.push_back(new_poly);
|
|
127
|
+
}
|
|
128
|
+
return new_res;
|
|
129
|
+
}
|
|
92
130
|
};
|
|
93
131
|
|
|
94
132
|
class PolygonFinder {
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
#pragma once
|
|
11
11
|
#include <limits>
|
|
12
12
|
#include <algorithm>
|
|
13
|
+
#include <string>
|
|
14
|
+
#include <sstream>
|
|
15
|
+
#include <iostream>
|
|
13
16
|
|
|
14
17
|
struct RectBounds {
|
|
15
18
|
int min_x = std::numeric_limits<int>::max();
|
|
@@ -37,4 +40,13 @@ struct RectBounds {
|
|
|
37
40
|
if (is_empty()) return 0;
|
|
38
41
|
return(max_x - min_x);
|
|
39
42
|
}
|
|
43
|
+
|
|
44
|
+
std::string to_string() const {
|
|
45
|
+
if (is_empty()) return "RectBounds: empty";
|
|
46
|
+
std::stringstream ss;
|
|
47
|
+
ss << "RectBounds ["
|
|
48
|
+
<< "X(min=" << min_x << ",max=" << max_x << ") "
|
|
49
|
+
<< "Y(min=" << min_y << ",max=" << max_y << ")]";
|
|
50
|
+
return ss.str();
|
|
51
|
+
}
|
|
40
52
|
};
|
|
@@ -98,7 +98,7 @@ std::vector<EndPoint*> Part::remove_adjacent_pairs(const std::vector<EndPoint*>&
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
void Part::orient()
|
|
101
|
-
{ if (this->size <= 1) {
|
|
101
|
+
{ if (this->size <= 1 || (this->size == 2 && this->inverts)) {
|
|
102
102
|
this->versus_ = 0;
|
|
103
103
|
} else {
|
|
104
104
|
this->versus_ = (this->tail->payload->y - this->head->payload->y) > 0 ? 1 : -1;
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
#include "PolygonFinder/src/polygon/finder/List.h"
|
|
25
25
|
#include "PolygonFinder/src/polygon/finder/Lists.cpp"
|
|
26
26
|
#include "PolygonFinder/src/polygon/finder/Lists.h"
|
|
27
|
+
#include "PolygonFinder/src/polygon/finder/PointPool.h"
|
|
28
|
+
#include "PolygonFinder/src/polygon/finder/PointPool.cpp"
|
|
27
29
|
#include "PolygonFinder/src/polygon/finder/Polygon.h"
|
|
28
30
|
#include "PolygonFinder/src/polygon/bitmaps/Bitmap.h"
|
|
29
31
|
#include "PolygonFinder/src/polygon/bitmaps/Bitmap.cpp"
|
|
@@ -219,7 +221,7 @@ class To_Ruby<ProcessResult*>
|
|
|
219
221
|
return_me[Symbol("treemap")] = tmapout;
|
|
220
222
|
rr->metadata = return_me;
|
|
221
223
|
|
|
222
|
-
// Protects objects 'out'
|
|
224
|
+
// Protects objects 'out' and 'return_me' linking them to the ruby instance preventing GC
|
|
223
225
|
// garbage collector to free them before the instance itself.
|
|
224
226
|
Rice::Object ruby_obj = rb_result;
|
|
225
227
|
ruby_obj.iv_set("@polygons_storage", out);
|
|
@@ -240,7 +242,6 @@ ProcessResult ruby_result_to_process_result(Rice::Object rb_result) {
|
|
|
240
242
|
|
|
241
243
|
Rice::Array rb_polygons = rb_result.iv_get("@polygons_storage");
|
|
242
244
|
for (size_t i = 0; i < rb_polygons.size(); ++i) {
|
|
243
|
-
// Cast esplicito a Rice::Hash per evitare il Proxy
|
|
244
245
|
Rice::Hash rb_poly = (Rice::Hash)rb_polygons[i];
|
|
245
246
|
Polygon poly;
|
|
246
247
|
// BOUNDS
|
|
@@ -371,9 +372,22 @@ void Init_cpp_polygon_finder() {
|
|
|
371
372
|
define_class<VerticalMerger, Merger>("CPPVerticalMerger")
|
|
372
373
|
.define_constructor(Constructor<VerticalMerger, int, std::vector<std::string>*>(), Arg("number_of_threads"), Arg("options") = nullptr, Arg("yield_gvl") = true);
|
|
373
374
|
|
|
374
|
-
|
|
375
|
-
|
|
375
|
+
Module mContrek = define_module("Contrek");
|
|
376
|
+
Module mCpp = define_module_under(mContrek, "Cpp");
|
|
377
|
+
Data_Type<RubyResult> rb_cResult = define_class_under<RubyResult>(mCpp, "CPPResult")
|
|
376
378
|
.define_constructor(Constructor<RubyResult>())
|
|
377
379
|
.define_method("polygons", [](RubyResult& rr) { return rr.polygons; })
|
|
378
|
-
.define_method("metadata", [](RubyResult& rr) { return rr.metadata; })
|
|
380
|
+
.define_method("metadata", [](RubyResult& rr) { return rr.metadata; })
|
|
381
|
+
.define_method("polygons=", [](RubyResult& rr, Object value) {
|
|
382
|
+
rr.polygons = value;
|
|
383
|
+
Rice::Data_Object<RubyResult> self(&rr);
|
|
384
|
+
self.iv_set("@polygons_storage", value);
|
|
385
|
+
return value;
|
|
386
|
+
})
|
|
387
|
+
.define_method("metadata=", [](RubyResult& rr, Object value) {
|
|
388
|
+
rr.metadata = (Rice::Hash)value;
|
|
389
|
+
Rice::Data_Object<RubyResult> self(&rr);
|
|
390
|
+
self.iv_set("@metadata_storage", value);
|
|
391
|
+
return value;
|
|
392
|
+
});
|
|
379
393
|
}
|
|
@@ -33,6 +33,16 @@ module Contrek
|
|
|
33
33
|
ChunkyBitmap.new(@raw, @module)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
def draw_rect(x:, y:, width:, height:, color: "o", filled: true)
|
|
37
|
+
(y...(y + height)).each do |row|
|
|
38
|
+
(x...(x + width)).each do |col|
|
|
39
|
+
if filled || row == y || row == y + height - 1 || col == x || col == x + width - 1
|
|
40
|
+
value_set(col, row, color)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
36
46
|
def draw_polygons(polygons)
|
|
37
47
|
polygons.each do |polygon|
|
|
38
48
|
[[:outer, "o"], [:inner, "i"]].each do |side, color|
|
|
@@ -47,16 +57,54 @@ module Contrek
|
|
|
47
57
|
end
|
|
48
58
|
end
|
|
49
59
|
|
|
50
|
-
def
|
|
60
|
+
def draw_numbered_polygons(polygons)
|
|
61
|
+
polygons.each do |polygon|
|
|
62
|
+
color = "A"
|
|
63
|
+
polygon[:outer].each_with_index do |position, index|
|
|
64
|
+
value_set(position[:x], position[:y], color)
|
|
65
|
+
color = next_color(color)
|
|
66
|
+
end
|
|
67
|
+
polygon[:inner].each do |sequence|
|
|
68
|
+
color = "a"
|
|
69
|
+
sequence.each_with_index do |position, index|
|
|
70
|
+
value_set(position[:x], position[:y], color)
|
|
71
|
+
color = next_color(color)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def to_terminal(label = nil)
|
|
78
|
+
puts label if label
|
|
51
79
|
puts " " + (0...@module).map { |i| (i % 10).to_s }.join
|
|
52
80
|
n = 0
|
|
53
81
|
@raw.scan(/.{1,#{@module}}/).each do |line|
|
|
54
|
-
|
|
82
|
+
colored_line = line.chars.map { |c| colorize_char(c) }.join
|
|
83
|
+
puts "#{n} #{colored_line}"
|
|
55
84
|
n += 1
|
|
56
85
|
n = 0 if n >= 10
|
|
57
86
|
end
|
|
58
87
|
puts
|
|
59
88
|
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def next_color(color)
|
|
93
|
+
return "A" if color == "Z"
|
|
94
|
+
return "a" if color == "z"
|
|
95
|
+
color.next
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def colorize_char(char)
|
|
99
|
+
case char
|
|
100
|
+
when "A".."Z"
|
|
101
|
+
"\e[91;1m#{char}\e[0m"
|
|
102
|
+
when "a".."z"
|
|
103
|
+
"\e[92;1m#{char}\e[0m"
|
|
104
|
+
else
|
|
105
|
+
char
|
|
106
|
+
end
|
|
107
|
+
end
|
|
60
108
|
end
|
|
61
109
|
end
|
|
62
110
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Contrek
|
|
2
|
+
module Cpp
|
|
3
|
+
class CPPResult
|
|
4
|
+
def polygons=(list)
|
|
5
|
+
@polygons_storage = list
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def metadata=(map)
|
|
9
|
+
@metadata_storage = {
|
|
10
|
+
width: 0,
|
|
11
|
+
height: 0,
|
|
12
|
+
benchmarks: {},
|
|
13
|
+
treemap: []
|
|
14
|
+
}.merge(map)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def points
|
|
18
|
+
raw_list = polygons.to_a
|
|
19
|
+
@to_points ||= raw_list.map do |polygon|
|
|
20
|
+
{outer: self.class.to_points(polygon[:outer]),
|
|
21
|
+
inner: polygon[:inner].map { |s| self.class.to_points(s) }}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def total_time
|
|
26
|
+
metadata[:benchmarks].values.sum
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.to_points(flat_polygon)
|
|
30
|
+
flat_polygon.each_slice(2).map { |x, y| {x: x, y: y} }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.to_numpy(polygons_data)
|
|
34
|
+
polygons_data.map do |poly|
|
|
35
|
+
{outer: poly[:outer].flat_map { |p| [p[:x], p[:y]] },
|
|
36
|
+
inner: (poly[:inner] || []).map { |hole| hole.flat_map { |p| [p[:x], p[:y]] } },
|
|
37
|
+
bounds: poly[:bounds]}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -69,10 +69,16 @@ module Contrek
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def intersect_part?(other_part)
|
|
72
|
+
intersect = false
|
|
72
73
|
other_part.each do |position|
|
|
73
|
-
|
|
74
|
+
if position.end_point.queues.include?(self)
|
|
75
|
+
intersect = true
|
|
76
|
+
false
|
|
77
|
+
else
|
|
78
|
+
true
|
|
79
|
+
end
|
|
74
80
|
end
|
|
75
|
-
|
|
81
|
+
intersect
|
|
76
82
|
end
|
|
77
83
|
|
|
78
84
|
def to_endpoints
|
|
@@ -80,7 +86,7 @@ module Contrek
|
|
|
80
86
|
end
|
|
81
87
|
|
|
82
88
|
def orient!
|
|
83
|
-
@versus = if size <= 1
|
|
89
|
+
@versus = if size <= 1 || (size == 2 && @inverts)
|
|
84
90
|
0
|
|
85
91
|
else
|
|
86
92
|
(tail.payload[:y] - head.payload[:y]).positive? ? 1 : -1
|
|
@@ -5,8 +5,8 @@ module Contrek
|
|
|
5
5
|
transpose(result)
|
|
6
6
|
if @tiles.size > 0
|
|
7
7
|
translate(result, @current_x)
|
|
8
|
-
adjust(result)
|
|
9
8
|
end
|
|
9
|
+
adjust(result)
|
|
10
10
|
super
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -26,7 +26,7 @@ module Contrek
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def transpose(result)
|
|
29
|
-
result.
|
|
29
|
+
result.metadata[:width], result.metadata[:height] = result.metadata[:height], result.metadata[:width]
|
|
30
30
|
result.polygons.each do |polygon|
|
|
31
31
|
polygon[:outer].each { |p| p[:x], p[:y] = p[:y], p[:x] }
|
|
32
32
|
polygon[:inner].each do |sequence|
|
|
@@ -73,7 +73,7 @@ module Contrek
|
|
|
73
73
|
@sequence_coords << coord
|
|
74
74
|
bounds.expand(x: coord[:x], y: coord[:y])
|
|
75
75
|
end
|
|
76
|
-
plot_node(next_node, root_node, bounds, versus) if @nodes > 0 && !next_node.nil?
|
|
76
|
+
plot_node(next_node, root_node, bounds, versus, @options[:strict_bounds]) if @nodes > 0 && !next_node.nil?
|
|
77
77
|
|
|
78
78
|
draw_sequence(bitmap, "X") unless bitmap.nil?
|
|
79
79
|
@polygons << {outer: @sequence_coords, inner: [], bounds: (bounds.to_h if @options[:bounds])}.compact if @sequence_coords.size >= 2
|
|
@@ -115,7 +115,7 @@ module Contrek
|
|
|
115
115
|
@sequence_coords << next_node.coords_entering_to(first, inner_v, Contrek::Finder::Node::INNER)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
plot_inner_node(next_node, inner_v, first, root_node) if !next_node.nil?
|
|
118
|
+
plot_inner_node(next_node, inner_v, first, root_node, options[:strict_bounds]) if !next_node.nil?
|
|
119
119
|
|
|
120
120
|
draw_sequence(bitmap, "+") unless bitmap.nil?
|
|
121
121
|
|
|
@@ -189,7 +189,7 @@ module Contrek
|
|
|
189
189
|
# inner way
|
|
190
190
|
# nodes in @plot_sequence
|
|
191
191
|
# coordinates in @sequence_coords
|
|
192
|
-
def plot_inner_node(node, versus, stop_at, start_node)
|
|
192
|
+
def plot_inner_node(node, versus, stop_at, start_node, strict_bounds = false)
|
|
193
193
|
node.outer_index = start_node.outer_index
|
|
194
194
|
node.inner_index = stop_at.inner_index
|
|
195
195
|
@root_nodes.delete(node)
|
|
@@ -210,6 +210,10 @@ module Contrek
|
|
|
210
210
|
@sequence_coords << next_node.coords_entering_to(node, versus, Contrek::Finder::Node::INNER)
|
|
211
211
|
end
|
|
212
212
|
end
|
|
213
|
+
elsif strict_bounds
|
|
214
|
+
first_is_max = ((node.y > last_node.y) == (versus == :a))
|
|
215
|
+
@sequence_coords << {y: node.y, x: (first_is_max ? last_node.max_x : last_node.min_x)}
|
|
216
|
+
@sequence_coords << {y: node.y, x: (first_is_max ? next_node.min_x : next_node.max_x)}
|
|
213
217
|
end
|
|
214
218
|
|
|
215
219
|
if node.track_uncomplete
|
|
@@ -219,11 +223,11 @@ module Contrek
|
|
|
219
223
|
end
|
|
220
224
|
|
|
221
225
|
return if next_node == stop_at
|
|
222
|
-
plot_inner_node(next_node, versus, stop_at, start_node)
|
|
226
|
+
plot_inner_node(next_node, versus, stop_at, start_node, strict_bounds)
|
|
223
227
|
end
|
|
224
228
|
|
|
225
229
|
# contour tracing core logic loop
|
|
226
|
-
def plot_node(node, start_node, bounds, versus = :a)
|
|
230
|
+
def plot_node(node, start_node, bounds, versus = :a, strict_bounds = false)
|
|
227
231
|
@root_nodes.delete(node)
|
|
228
232
|
|
|
229
233
|
node.outer_index = start_node.outer_index
|
|
@@ -252,13 +256,18 @@ module Contrek
|
|
|
252
256
|
@inner_plot.contains(node) ? @inner_plot.delete(node) : @inner_plot << node
|
|
253
257
|
end
|
|
254
258
|
end
|
|
259
|
+
elsif strict_bounds
|
|
260
|
+
is_down = node.y > last_node.y
|
|
261
|
+
is_a = (versus == :a)
|
|
262
|
+
@sequence_coords << {y: node.y, x: ((is_down == is_a) ? last_node.min_x : last_node.max_x)}
|
|
263
|
+
@sequence_coords << {y: node.y, x: ((is_down == is_a) ? next_node.max_x : next_node.min_x)}
|
|
255
264
|
end
|
|
256
265
|
# exit if root_node
|
|
257
266
|
|
|
258
267
|
if node == start_node
|
|
259
268
|
return if node.track_complete
|
|
260
269
|
end
|
|
261
|
-
plot_node(next_node, start_node, bounds, versus)
|
|
270
|
+
plot_node(next_node, start_node, bounds, versus, strict_bounds)
|
|
262
271
|
end
|
|
263
272
|
|
|
264
273
|
def add_node(node, offset)
|
|
@@ -4,7 +4,7 @@ module Contrek
|
|
|
4
4
|
module Finder
|
|
5
5
|
class PolygonFinder
|
|
6
6
|
def initialize(bitmap, matcher, test_bitmap = nil, options = {})
|
|
7
|
-
@options = {versus: :a}.merge(options)
|
|
7
|
+
@options = {versus: :a, strict_bounds: false}.merge(options)
|
|
8
8
|
sanitize_options
|
|
9
9
|
@source_bitmap = bitmap
|
|
10
10
|
@matcher = matcher
|
data/lib/contrek/version.rb
CHANGED
data/lib/contrek.rb
CHANGED
|
@@ -49,7 +49,7 @@ require "contrek/cpp/cpp_concurrent_finder"
|
|
|
49
49
|
require "contrek/cpp/cpp_concurrent_merger"
|
|
50
50
|
require "contrek/cpp/cpp_concurrent_horizontal_merger"
|
|
51
51
|
require "contrek/cpp/cpp_concurrent_vertical_merger"
|
|
52
|
-
require "contrek/
|
|
52
|
+
require "contrek/cpp/cpp_result"
|
|
53
53
|
|
|
54
54
|
module Contrek
|
|
55
55
|
class << self
|
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.1.
|
|
4
|
+
version: 1.1.6
|
|
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-03-
|
|
11
|
+
date: 2026-03-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec
|
|
@@ -141,9 +141,17 @@ files:
|
|
|
141
141
|
- Rakefile
|
|
142
142
|
- contrek.gemspec
|
|
143
143
|
- contrek.png
|
|
144
|
+
- docs/images/strict_bounds_off.png
|
|
145
|
+
- docs/images/strict_bounds_on.png
|
|
146
|
+
- docs/images/stripes/whole.png
|
|
147
|
+
- docs/images/stripes/whole_0.png
|
|
148
|
+
- docs/images/stripes/whole_256.png
|
|
149
|
+
- docs/images/stripes/whole_512.png
|
|
150
|
+
- docs/images/stripes/whole_768.png
|
|
144
151
|
- ext/cpp_polygon_finder/PolygonFinder/CMakeLists.txt
|
|
145
152
|
- ext/cpp_polygon_finder/PolygonFinder/LICENSE_AGPL.txt
|
|
146
153
|
- ext/cpp_polygon_finder/PolygonFinder/examples/example.cpp
|
|
154
|
+
- ext/cpp_polygon_finder/PolygonFinder/images/graphs_1024x1024.png
|
|
147
155
|
- ext/cpp_polygon_finder/PolygonFinder/images/labyrinth.png
|
|
148
156
|
- ext/cpp_polygon_finder/PolygonFinder/images/sample_10240x10240.png
|
|
149
157
|
- ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h
|
|
@@ -170,6 +178,8 @@ files:
|
|
|
170
178
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h
|
|
171
179
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp
|
|
172
180
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h
|
|
181
|
+
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PointPool.cpp
|
|
182
|
+
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PointPool.h
|
|
173
183
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Polygon.h
|
|
174
184
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp
|
|
175
185
|
- ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h
|
|
@@ -246,6 +256,7 @@ files:
|
|
|
246
256
|
- lib/contrek/cpp/cpp_concurrent_horizontal_merger.rb
|
|
247
257
|
- lib/contrek/cpp/cpp_concurrent_merger.rb
|
|
248
258
|
- lib/contrek/cpp/cpp_concurrent_vertical_merger.rb
|
|
259
|
+
- lib/contrek/cpp/cpp_result.rb
|
|
249
260
|
- lib/contrek/finder/bounds.rb
|
|
250
261
|
- lib/contrek/finder/concurrent/clipped_polygon_finder.rb
|
|
251
262
|
- lib/contrek/finder/concurrent/cluster.rb
|
|
@@ -283,7 +294,6 @@ files:
|
|
|
283
294
|
- lib/contrek/reducers/reducer.rb
|
|
284
295
|
- lib/contrek/reducers/uniq_reducer.rb
|
|
285
296
|
- lib/contrek/reducers/visvalingam_reducer.rb
|
|
286
|
-
- lib/contrek/results/cpp_result.rb
|
|
287
297
|
- lib/contrek/version.rb
|
|
288
298
|
homepage: https://github.com/runout77/contrek
|
|
289
299
|
licenses:
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
module Contrek
|
|
2
|
-
module Results
|
|
3
|
-
class CPPFinder::Result
|
|
4
|
-
def points
|
|
5
|
-
raw_list = polygons.to_a
|
|
6
|
-
@to_points ||= raw_list.map do |polygon|
|
|
7
|
-
{outer: self.class.to_points(polygon[:outer]),
|
|
8
|
-
inner: polygon[:inner].map { |s| self.class.to_points(s) }}
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def total_time
|
|
13
|
-
metadata[:benchmarks].values.sum
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def self.to_points(flat_polygon)
|
|
17
|
-
flat_polygon.each_slice(2).map { |x, y| {x: x, y: y} }
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|