contrek 1.1.3 → 1.1.5
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 +11 -1
- data/Gemfile.lock +1 -1
- data/README.md +37 -18
- data/ext/cpp_polygon_finder/PolygonFinder/examples/example.cpp +3 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +71 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.h +2 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.cpp +13 -47
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.h +2 -11
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.cpp +64 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.h +37 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RemoteFastPngBitmap.cpp +2 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +38 -20
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +4 -13
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +15 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +2 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +12 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +3 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/EndPoint.h +3 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +10 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.h +8 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.cpp +22 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.h +19 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.cpp +46 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.h +24 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +4 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +3 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Position.cpp +2 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queue.h +5 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.cpp +64 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.h +24 -0
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +91 -0
- data/lib/contrek/bitmaps/{custom_bitmap.rb → raw_bitmap.rb} +1 -1
- data/lib/contrek/cpp/cpp_concurrent_horizontal_merger.rb +9 -0
- data/lib/contrek/cpp/cpp_concurrent_merger.rb +9 -0
- data/lib/contrek/cpp/cpp_concurrent_vertical_merger.rb +9 -0
- data/lib/contrek/finder/concurrent/cursor.rb +2 -0
- data/lib/contrek/finder/concurrent/finder.rb +8 -5
- data/lib/contrek/finder/concurrent/horizontal_merger.rb +10 -0
- data/lib/contrek/finder/concurrent/merger.rb +52 -0
- data/lib/contrek/finder/concurrent/tile.rb +0 -2
- data/lib/contrek/finder/concurrent/vertical_merger.rb +44 -0
- data/lib/contrek/finder/node.rb +38 -21
- data/lib/contrek/finder/node_cluster.rb +12 -15
- data/lib/contrek/finder/polygon_finder.rb +2 -1
- data/lib/contrek/reducers/linear_reducer.rb +1 -1
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +7 -1
- metadata +20 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 22d05e676f5c4a0f22f02ec2e915257bf1f5da224d468cb0e2f46fc397360242
|
|
4
|
+
data.tar.gz: '0997b63d2839cc3ba613884eff2e3a565d96502e4606db0bb72d2aee228e514d'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57bf5e08937ea8fb4cee39e383a8f760e3a36714643c583408af6d02c7fbb84515af8d3089c753319677b8d5223c7a6a07f505c4356e21cd10570abbec358210
|
|
7
|
+
data.tar.gz: 0ec4babf59251515b05857a459adfd7365b930feaf58b17ca2215ab4b534abe3ac41ae45e7643270ec8acba4c699fbcd311b12eb3ddab6789e5dd1b7275ad220
|
data/CHANGELOG.md
CHANGED
|
@@ -52,4 +52,14 @@ All notable changes to this project will be documented in this file.
|
|
|
52
52
|
## [1.1.3] - 2026-02-21
|
|
53
53
|
### Changed
|
|
54
54
|
- Added support for 8-way pixel connectivity (omnidirectional) in addition to the standard 4-way mode.
|
|
55
|
-
- Optimized C++ and Ruby algorithms for initial spatial pixel tracking to improve performance.
|
|
55
|
+
- Optimized C++ and Ruby algorithms for initial spatial pixel tracking to improve performance.
|
|
56
|
+
|
|
57
|
+
## [1.1.4] - 2026-02-28
|
|
58
|
+
### Changed
|
|
59
|
+
- Fixed an infinite loop bug in multithreading during inner sequence joining in Omnidirectional mode.
|
|
60
|
+
- Optimized C++ and Ruby algorithms for initial spatial tangential sequence determination.
|
|
61
|
+
|
|
62
|
+
## [1.1.5] - 2026-03-08
|
|
63
|
+
### Changed
|
|
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
|
+
- **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.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Contrek
|
|
2
|
-
Contrek is a Ruby gem powered by a <u>standalone C++17 core library</u> for fast contour tracing and edge detection in PNG images. It employs a **topological approach** to extract polygonal contours, representing shapes as a connected graph of shared endpoints. This ensures perfect adjacency and structural integrity for shape analysis and raster-to-vector workflows, such as PNG to SVG conversion, managed via libspng (0.7.4) with multithreading support.
|
|
2
|
+
Contrek is a Ruby gem powered by a <u>standalone C++17 core library</u> for fast contour tracing and edge detection in PNG images and raw memory streams. It employs a **topological approach** to extract polygonal contours, representing shapes as a connected graph of shared endpoints. This ensures perfect adjacency and structural integrity for shape analysis and raster-to-vector workflows, such as PNG to SVG conversion, managed via libspng (0.7.4) with multithreading support.
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
## About Contrek library
|
|
6
6
|
|
|
7
|
-
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.
|
|
7
|
+
Contrek (**con**tour **trek**king) simply scans your png/raw 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.
|
|
8
8
|
|
|
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
|

|
|
@@ -86,6 +86,24 @@ You can also read base64 png images
|
|
|
86
86
|
png_bitmap = CPPRemotePngBitMap.new("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==")
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
You can process from a raw stream
|
|
90
|
+
```ruby
|
|
91
|
+
raw_bitmap = CPPRawBitMap.new
|
|
92
|
+
# set 20 as width, 30 as height, 4 as bytes per pixel and clears the content (true)
|
|
93
|
+
raw_bitmap.define(20,30,4,true)
|
|
94
|
+
# draws a polygon
|
|
95
|
+
4.upto(5) do |y|
|
|
96
|
+
5.upto(8) do |x|
|
|
97
|
+
raw_bitmap.draw_pixel(x, y, 1, 0, 0, 0);
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
not_matcher = CPPRGBNotMatcher.new(raw_bitmap.rgb_value_at(0, 0))
|
|
101
|
+
result = CPPPolygonFinder.new(raw_bitmap, not_matcher, nil,{compress: {uniq: true, linear: true}}).process_info
|
|
102
|
+
puts result.points.inspect
|
|
103
|
+
=>
|
|
104
|
+
[{:outer=>[{:x=>5, :y=>4}, {:x=>5, :y=>5}, {:x=>8, :y=>5}, {:x=>8, :y=>4}], :inner=>[]}]
|
|
105
|
+
```
|
|
106
|
+
|
|
89
107
|
Multithreaded contour processing is supported. However, on Ruby MRI (the standard Ruby implementation, at least up to 3.x), the Global Interpreter Lock (GIL) prevents more than one thread from executing Ruby code simultaneously. As a consequence, execution remains effectively serialized even on multicore systems, unless the gem is used under JRuby or TruffleRuby (not tested).
|
|
90
108
|
|
|
91
109
|
```ruby
|
|
@@ -112,7 +130,7 @@ Regarding multithreading:
|
|
|
112
130
|
|
|
113
131
|
- The treemap option is currently ignored (multithreaded treemap support will be introduced in upcoming revisions).
|
|
114
132
|
|
|
115
|
-
By not declaring native option CPP Multithreading optimized code is used. In the above example a [105 MP image](spec/files/images/sample_10240x10240.png) is examined by 4 threads working on 4 tiles (total compute time about 1.
|
|
133
|
+
By not declaring native option CPP Multithreading optimized code is used. In the above example a [105 MP image](spec/files/images/sample_10240x10240.png) is examined by 4 threads working on 4 tiles (total compute time about 1.2 secs with image load).
|
|
116
134
|
|
|
117
135
|
```ruby
|
|
118
136
|
result = Contrek.contour!(
|
|
@@ -126,11 +144,11 @@ result = Contrek.contour!(
|
|
|
126
144
|
)
|
|
127
145
|
puts result.metadata[:benchmarks].inspect
|
|
128
146
|
|
|
129
|
-
{ compress:
|
|
130
|
-
init:
|
|
131
|
-
inner:
|
|
132
|
-
outer:
|
|
133
|
-
total:
|
|
147
|
+
{ compress: 26.37,
|
|
148
|
+
init: 354.339,
|
|
149
|
+
inner: 17.9427,
|
|
150
|
+
outer: 93.6252,
|
|
151
|
+
total: 380.709
|
|
134
152
|
}
|
|
135
153
|
|
|
136
154
|
```
|
|
@@ -288,23 +306,24 @@ This process is applied recursively, merging bands until a single final band rem
|
|
|
288
306
|
One of the most complex test you can find under the spec folder is named "scans poly 1200x800", scans this [image](spec/files/images/sample_1200x800.png) computing coordinates to draw polygons drawn in this [result](spec/files/stored_samples/sample_1200x800.png).
|
|
289
307
|
On pure ruby implementation kept time
|
|
290
308
|
```ruby
|
|
291
|
-
{ :scan=>
|
|
292
|
-
:build_tangs_sequence=>
|
|
293
|
-
:plot=>
|
|
309
|
+
{ :scan=>775.435,
|
|
310
|
+
:build_tangs_sequence=>38.916,
|
|
311
|
+
:plot=>101.876,
|
|
294
312
|
:compress=>0.002,
|
|
295
|
-
:total=>
|
|
313
|
+
:total=>916.229
|
|
314
|
+
}
|
|
296
315
|
```
|
|
297
316
|
This the one for the native C++
|
|
298
317
|
```ruby
|
|
299
|
-
{ scan:
|
|
300
|
-
build_tangs_sequence:
|
|
301
|
-
plot:
|
|
302
|
-
compress: 0.
|
|
303
|
-
total:
|
|
318
|
+
{ scan: 5.077878999999999,
|
|
319
|
+
build_tangs_sequence: 0.697222999999999,
|
|
320
|
+
plot: 2.00479,
|
|
321
|
+
compress: 0.00071,
|
|
322
|
+
total: 7.780602
|
|
304
323
|
}
|
|
305
324
|
```
|
|
306
325
|
|
|
307
|
-
About
|
|
326
|
+
About 130x faster. Times are in microseconds; system: AMD Ryzen 7 3700X 8-Core Processor (BogoMIPS: 7199,99) on Ubuntu distro.
|
|
308
327
|
|
|
309
328
|
## 🛠 C++ Standalone Library Usage
|
|
310
329
|
|
|
@@ -21,7 +21,9 @@ void run_test() {
|
|
|
21
21
|
// test_suite.test_c();
|
|
22
22
|
// test_suite.test_d();
|
|
23
23
|
// test_suite.test_e();
|
|
24
|
-
test_suite.test_f();
|
|
24
|
+
// test_suite.test_f();
|
|
25
|
+
// test_suite.test_g();
|
|
26
|
+
// test_suite.test_h();
|
|
25
27
|
std::cout << "compute time =" << cpu_timer.stop() << std::endl;
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -70,7 +70,7 @@ inline std::unique_ptr<ProcessResult> trace(const std::string& image_path, const
|
|
|
70
70
|
if (m.flag) internal_args.emplace_back(m.arg);
|
|
71
71
|
}
|
|
72
72
|
internal_args.push_back("--number_of_tiles=" + std::to_string(cfg.tiles));
|
|
73
|
-
if(cfg.connectivity_mode == Connectivity::OMNIDIRECTIONAL) {
|
|
73
|
+
if (cfg.connectivity_mode == Connectivity::OMNIDIRECTIONAL) {
|
|
74
74
|
internal_args.push_back("--connectivity=" + std::to_string(8));
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
#include "polygon/finder/concurrent/ClippedPolygonFinder.h"
|
|
19
19
|
#include "polygon/bitmaps/Bitmap.h"
|
|
20
20
|
#include "polygon/bitmaps/FastPngBitmap.h"
|
|
21
|
+
#include "polygon/bitmaps/RawBitmap.h"
|
|
21
22
|
#include "polygon/bitmaps/RemoteFastPngBitmap.h"
|
|
22
23
|
#include "polygon/matchers/Matcher.h"
|
|
23
24
|
#include "polygon/matchers/RGBMatcher.h"
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
#include "polygon/matchers/ValueNotMatcher.h"
|
|
26
27
|
#include "polygon/finder/optionparser.h"
|
|
27
28
|
#include "polygon/finder/concurrent/Finder.h"
|
|
29
|
+
#include "polygon/finder/concurrent/HorizontalMerger.h"
|
|
28
30
|
#include "polygon/finder/concurrent/Sequence.h"
|
|
29
31
|
#include "polygon/finder/concurrent/Position.h"
|
|
30
32
|
#include "polygon/finder/Polygon.h"
|
|
@@ -141,7 +143,7 @@ void Tests::test_d()
|
|
|
141
143
|
FastPngBitmap png_bitmap("../images/sample_10240x10240.png");
|
|
142
144
|
// FastPngBitmap png_bitmap("images/labyrinth.png");
|
|
143
145
|
std::cout << "image_w=" << png_bitmap.w() << " image_h=" << png_bitmap.h() << std::endl;
|
|
144
|
-
std::cout << "
|
|
146
|
+
std::cout << "image reading time =" << cpu_timer.stop() << std::endl;
|
|
145
147
|
|
|
146
148
|
int color = png_bitmap.value_at(0, 0);
|
|
147
149
|
std::cout << "color =" << color << std::endl;
|
|
@@ -169,7 +171,7 @@ void Tests::test_e()
|
|
|
169
171
|
Finder pl(2, &png_bitmap, ¬_matcher, &arguments);
|
|
170
172
|
ProcessResult *o = pl.process_info();
|
|
171
173
|
o->print_info();
|
|
172
|
-
std::cout << "polygons =" << o->groups << std::endl;
|
|
174
|
+
std::cout << "polygons = " << o->groups << std::endl;
|
|
173
175
|
delete o;
|
|
174
176
|
}
|
|
175
177
|
|
|
@@ -184,3 +186,70 @@ void Tests::test_f()
|
|
|
184
186
|
std::cout << "image_w=" << bitmap.w() << " image_h=" << bitmap.h() << std::endl;
|
|
185
187
|
std::cout << "load_error=" << bitmap.error() << std::endl;
|
|
186
188
|
}
|
|
189
|
+
|
|
190
|
+
void Tests::test_g()
|
|
191
|
+
{ RawBitmap raw_bitmap;
|
|
192
|
+
raw_bitmap.define(50, 50, 4);
|
|
193
|
+
|
|
194
|
+
// draw a polygon start_x = 5, start_y = 4, end_x = 8, end_y = 5
|
|
195
|
+
for (int y : {4, 5}) {
|
|
196
|
+
for (int x = 5; x <= 8; ++x) {
|
|
197
|
+
raw_bitmap.draw_pixel(x, y, 1, 0, 0);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
int color = raw_bitmap.rgb_value_at(0, 0);
|
|
202
|
+
std::cout << "color = " << color << std::endl;
|
|
203
|
+
RGBNotMatcher not_matcher(color);
|
|
204
|
+
|
|
205
|
+
std::vector<std::string> arguments = {"--versus=a", "--compress_uniq", "--number_of_tiles=2"};
|
|
206
|
+
Finder pl(2, &raw_bitmap, ¬_matcher, &arguments);
|
|
207
|
+
ProcessResult *o = pl.process_info();
|
|
208
|
+
// o->print_info();
|
|
209
|
+
// o->print_polygons();
|
|
210
|
+
std::cout << "polygons = " << o->groups << std::endl;
|
|
211
|
+
delete o;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
void Tests::test_h()
|
|
215
|
+
{ std::string left =
|
|
216
|
+
"0000000000" \
|
|
217
|
+
"0000000000" \
|
|
218
|
+
"00 " \
|
|
219
|
+
"00 " \
|
|
220
|
+
"00 " \
|
|
221
|
+
"00 " \
|
|
222
|
+
"00 " \
|
|
223
|
+
"0000000000" \
|
|
224
|
+
"0000000000";
|
|
225
|
+
std::vector<std::string> arguments = {"--versus=a", "--compress_uniq"};
|
|
226
|
+
ValueNotMatcher matcher(' ');
|
|
227
|
+
Bitmap b_left(left, 10);
|
|
228
|
+
PolygonFinder pl_left(&b_left, &matcher, nullptr, &arguments);
|
|
229
|
+
ProcessResult *left_result = pl_left.process_info();
|
|
230
|
+
|
|
231
|
+
std::string right =
|
|
232
|
+
"0000000000" \
|
|
233
|
+
"0000000000" \
|
|
234
|
+
" 0000" \
|
|
235
|
+
" 0000" \
|
|
236
|
+
" 0000" \
|
|
237
|
+
" 0000" \
|
|
238
|
+
" 0000" \
|
|
239
|
+
"0000000000" \
|
|
240
|
+
"0000000000";
|
|
241
|
+
Bitmap b_right(right, 10);
|
|
242
|
+
PolygonFinder pl_right(&b_right, &matcher, nullptr, &arguments);
|
|
243
|
+
ProcessResult *right_result = pl_right.process_info();
|
|
244
|
+
|
|
245
|
+
std::vector<std::string> merger_arguments = {};
|
|
246
|
+
HorizontalMerger hmerger(1, &arguments);
|
|
247
|
+
hmerger.add_tile(*left_result);
|
|
248
|
+
hmerger.add_tile(*right_result);
|
|
249
|
+
ProcessResult *merged_result = hmerger.process_info();
|
|
250
|
+
merged_result->print_polygons();
|
|
251
|
+
|
|
252
|
+
delete merged_result;
|
|
253
|
+
delete left_result;
|
|
254
|
+
delete right_result;
|
|
255
|
+
}
|
|
@@ -20,72 +20,38 @@
|
|
|
20
20
|
#include "FastPngBitmap.h"
|
|
21
21
|
#include "spng.h"
|
|
22
22
|
|
|
23
|
-
FastPngBitmap::FastPngBitmap(std::string filename) :
|
|
24
|
-
if (filename.length() > 0)
|
|
25
|
-
|
|
26
|
-
if (file.is_open())
|
|
27
|
-
|
|
23
|
+
FastPngBitmap::FastPngBitmap(std::string filename) : RawBitmap() {
|
|
24
|
+
if (filename.length() > 0) {
|
|
25
|
+
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
|
26
|
+
if (file.is_open()) {
|
|
27
|
+
std::streamsize size = file.tellg();
|
|
28
28
|
file.seekg(0, std::ios::beg);
|
|
29
29
|
std::vector<unsigned char> file_buffer(size);
|
|
30
|
-
if (!file.read(reinterpret_cast<char*>(file_buffer.data()), size))
|
|
31
|
-
|
|
30
|
+
if (!file.read(reinterpret_cast<char*>(file_buffer.data()), size)) {
|
|
31
|
+
this->png_error = -1;
|
|
32
32
|
} else {
|
|
33
33
|
spng_ctx *ctx = spng_ctx_new(0);
|
|
34
34
|
spng_set_png_buffer(ctx, file_buffer.data(), file_buffer.size());
|
|
35
35
|
struct spng_ihdr ihdr;
|
|
36
36
|
spng_get_ihdr(ctx, &ihdr);
|
|
37
|
-
this->width = ihdr.width;
|
|
38
|
-
this->height = ihdr.height;
|
|
39
37
|
size_t out_size;
|
|
40
38
|
spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &out_size);
|
|
41
|
-
this->
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
this->define(ihdr.width, ihdr.height, 4, false);
|
|
40
|
+
unsigned char* raw_ptr = this->image.get();
|
|
41
|
+
madvise(raw_ptr, out_size, MADV_HUGEPAGE);
|
|
42
|
+
int error = spng_decode_image(ctx, raw_ptr, out_size, SPNG_FMT_RGBA8, SPNG_DECODE_TRNS);
|
|
44
43
|
spng_ctx_free(ctx);
|
|
45
44
|
this->png_error = error;
|
|
46
45
|
}
|
|
46
|
+
} else {
|
|
47
|
+
throw std::runtime_error("Unable to open file: " + filename);
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
bool FastPngBitmap::pixel_match(int x, int y, Matcher *matcher)
|
|
52
|
-
{ int32_t index = ((y * width) + x) * 4;
|
|
53
|
-
unsigned int color;
|
|
54
|
-
unsigned char *red = &image[index];
|
|
55
|
-
std::memcpy(&color, red, 3);
|
|
56
|
-
if (typeid(*matcher) == typeid(RGBMatcher)) return((reinterpret_cast<RGBMatcher*>(matcher))->match(color));
|
|
57
|
-
if (typeid(*matcher) == typeid(RGBNotMatcher)) return((reinterpret_cast<RGBNotMatcher*>(matcher))->match(color));
|
|
58
|
-
return(false);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
int FastPngBitmap::w() {
|
|
62
|
-
return this->width;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
int FastPngBitmap::h() {
|
|
66
|
-
return this->height;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
52
|
int FastPngBitmap::error() {
|
|
70
53
|
return this->png_error;
|
|
71
54
|
}
|
|
72
|
-
char FastPngBitmap::value_at(int x, int y) {
|
|
73
|
-
return(0);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// source image format RGBA => returning uint ABGR
|
|
77
|
-
unsigned int FastPngBitmap::rgb_value_at(int x, int y) {
|
|
78
|
-
uint32_t index = (uint32_t(y) * width + x) * 4;
|
|
79
|
-
return *reinterpret_cast<const uint32_t*>(&image[index]);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const unsigned char* FastPngBitmap::get_row_ptr(int y) const {
|
|
83
|
-
return image.data() + (static_cast<size_t>(y) * static_cast<size_t>(width) * 4);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
int FastPngBitmap::get_bytes_per_pixel() const {
|
|
87
|
-
return 4; // RGBA
|
|
88
|
-
}
|
|
89
55
|
|
|
90
56
|
void FastPngBitmap::loadFile(std::vector<unsigned char>& buffer, const std::string& filename) // designed for loading files from hard disk in an std::vector
|
|
91
57
|
{ std::ifstream file(filename.c_str(), std::ios::in|std::ios::binary|std::ios::ate);
|
|
@@ -11,29 +11,20 @@
|
|
|
11
11
|
#include <vector>
|
|
12
12
|
#include <cstddef>
|
|
13
13
|
#include <string>
|
|
14
|
-
#include "
|
|
14
|
+
#include "RawBitmap.h"
|
|
15
15
|
|
|
16
16
|
class RGBMatcher;
|
|
17
17
|
|
|
18
18
|
static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
19
19
|
|
|
20
|
-
class FastPngBitmap : public
|
|
20
|
+
class FastPngBitmap : public RawBitmap {
|
|
21
21
|
public:
|
|
22
22
|
explicit FastPngBitmap(std::string filename);
|
|
23
|
-
bool pixel_match(int x, int y, Matcher *matcher);
|
|
24
|
-
int h();
|
|
25
|
-
int w();
|
|
26
23
|
virtual int error();
|
|
27
|
-
char value_at(int x, int y);
|
|
28
|
-
unsigned int rgb_value_at(int x, int y);
|
|
29
|
-
const unsigned char* get_row_ptr(int y) const;
|
|
30
|
-
int get_bytes_per_pixel() const;
|
|
31
24
|
|
|
32
25
|
protected:
|
|
33
26
|
std::vector<unsigned char> buffer;
|
|
34
27
|
std::vector<unsigned char> base64_decode(std::string &encoded_string);
|
|
35
|
-
std::vector<unsigned char> image;
|
|
36
|
-
unsigned int width, height;
|
|
37
28
|
int png_error;
|
|
38
29
|
int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true);
|
|
39
30
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* RawBitmap.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 <memory>
|
|
11
|
+
#include "RawBitmap.h"
|
|
12
|
+
|
|
13
|
+
RawBitmap::RawBitmap() : Bitmap("", 0),
|
|
14
|
+
width(0),
|
|
15
|
+
height(0) {
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
int RawBitmap::w() {
|
|
19
|
+
return this->width;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
int RawBitmap::h() {
|
|
23
|
+
return this->height;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
char RawBitmap::value_at(int x, int y) {
|
|
27
|
+
return(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const unsigned char* RawBitmap::get_row_ptr(int y) const {
|
|
31
|
+
return image.get() + (static_cast<size_t>(y) * static_cast<size_t>(width) * 4);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
int RawBitmap::get_bytes_per_pixel() const {
|
|
35
|
+
return this->bpp;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// source image format RGBA => returning uint ABGR
|
|
39
|
+
unsigned int RawBitmap::rgb_value_at(int x, int y) {
|
|
40
|
+
uint32_t index = (static_cast<uint32_t>(y) * width + x) * 4;
|
|
41
|
+
return *reinterpret_cast<const uint32_t*>(&image[index]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
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;
|
|
46
|
+
unsigned char* p = &image[(static_cast<size_t>(y) * width + x) * bpp];
|
|
47
|
+
p[0] = r;
|
|
48
|
+
if (bpp > 1) p[1] = g;
|
|
49
|
+
if (bpp > 2) p[2] = b;
|
|
50
|
+
if (bpp > 3) p[3] = a;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
uint32_t RawBitmap::define(uint width, uint height, int bytes_per_pixel, bool clear)
|
|
54
|
+
{ this->width = width;
|
|
55
|
+
this->height = height;
|
|
56
|
+
this->bpp = bytes_per_pixel;
|
|
57
|
+
uint32_t dimension = (static_cast<uint32_t>(width) * static_cast<uint32_t>(height)) * bytes_per_pixel;
|
|
58
|
+
this->image = std::make_unique<unsigned char[]>(dimension);
|
|
59
|
+
if (clear) {
|
|
60
|
+
std::fill(image.get(), image.get() + dimension, 0);
|
|
61
|
+
}
|
|
62
|
+
return dimension;
|
|
63
|
+
}
|
|
64
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* RawBitmap.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 <vector>
|
|
12
|
+
#include <memory>
|
|
13
|
+
#include <cstddef>
|
|
14
|
+
#include <string>
|
|
15
|
+
#include "Bitmap.h"
|
|
16
|
+
|
|
17
|
+
class RGBMatcher;
|
|
18
|
+
|
|
19
|
+
class RawBitmap : public Bitmap {
|
|
20
|
+
public:
|
|
21
|
+
explicit RawBitmap();
|
|
22
|
+
int h();
|
|
23
|
+
int w();
|
|
24
|
+
char value_at(int x, int y);
|
|
25
|
+
unsigned int rgb_value_at(int x, int y);
|
|
26
|
+
const unsigned char* get_row_ptr(int y) const;
|
|
27
|
+
int get_bytes_per_pixel() const;
|
|
28
|
+
uint32_t define(uint width, uint height, int bytes_per_pixel, bool clear = false);
|
|
29
|
+
void draw_pixel(int x, int y, unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255);
|
|
30
|
+
|
|
31
|
+
protected:
|
|
32
|
+
std::unique_ptr<unsigned char[]> image;
|
|
33
|
+
unsigned int width, height;
|
|
34
|
+
|
|
35
|
+
private:
|
|
36
|
+
int bpp;
|
|
37
|
+
};
|
|
@@ -30,12 +30,10 @@ RemoteFastPngBitmap::RemoteFastPngBitmap(std::string *dataurl) : FastPngBitmap("
|
|
|
30
30
|
struct spng_ihdr ihdr;
|
|
31
31
|
int error = spng_get_ihdr(ctx, &ihdr);
|
|
32
32
|
if (!error) {
|
|
33
|
-
this->width = ihdr.width;
|
|
34
|
-
this->height = ihdr.height;
|
|
35
33
|
size_t out_size; // RGBA8 dimension
|
|
36
34
|
spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &out_size);
|
|
37
|
-
this->
|
|
38
|
-
error = spng_decode_image(ctx, image.
|
|
35
|
+
this->define(ihdr.width, ihdr.height, 4, false);
|
|
36
|
+
error = spng_decode_image(ctx, image.get(), out_size, SPNG_FMT_RGBA8, SPNG_DECODE_TRNS);
|
|
39
37
|
}
|
|
40
38
|
if (error != 0) {
|
|
41
39
|
std::cout << "spng error: " << spng_strerror(error) << std::endl;
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
#include "Node.h"
|
|
18
18
|
#include "NodeCluster.h"
|
|
19
19
|
|
|
20
|
-
Node::Node(int min_x, int max_x, int y, char name)
|
|
20
|
+
Node::Node(int min_x, int max_x, int y, NodeCluster* cluster, char name)
|
|
21
21
|
: start_point(min_x, y),
|
|
22
|
-
end_point(max_x, y)
|
|
22
|
+
end_point(max_x, y),
|
|
23
|
+
cluster(cluster) {
|
|
23
24
|
this->name = name;
|
|
24
25
|
this->min_x = min_x;
|
|
25
26
|
this->max_x = max_x;
|
|
@@ -67,24 +68,14 @@ void Node::precalc_tangs_sequences(NodeCluster& cluster) {
|
|
|
67
68
|
}
|
|
68
69
|
// --- CLOCKWISE (UP) ---
|
|
69
70
|
for (int upper_pos = this->upper_start; upper_pos <= this->upper_end; upper_pos++) {
|
|
70
|
-
|
|
71
|
-
tangs_sequence.emplace_back(NodeDescriptor{
|
|
72
|
-
&t_node,
|
|
73
|
-
Tangent{ &t_node.end_point, OMAX},
|
|
74
|
-
Tangent{ &t_node.start_point, OMIN}
|
|
75
|
-
});
|
|
71
|
+
tangs_sequence.push_back(-(upper_pos + 1));
|
|
76
72
|
}
|
|
77
73
|
if (this->lower_end >= 0) {
|
|
78
74
|
this->down_indexer = (cluster.vert_nodes[y + T_DOWN][this->lower_start].abs_x_index + lower_size + upper_size - 1);
|
|
79
75
|
}
|
|
80
76
|
// --- COUNTER-CLOCKWISE (DOWN) ---
|
|
81
77
|
for (int lower_pos = this->lower_end; lower_pos >= this->lower_start; lower_pos--) {
|
|
82
|
-
|
|
83
|
-
tangs_sequence.emplace_back(NodeDescriptor{
|
|
84
|
-
&t_node,
|
|
85
|
-
Tangent{&t_node.start_point, OMIN},
|
|
86
|
-
Tangent{&t_node.end_point, OMAX}
|
|
87
|
-
});
|
|
78
|
+
tangs_sequence.push_back(lower_pos);
|
|
88
79
|
}
|
|
89
80
|
this->tangs_count = this->tangs_sequence.size();
|
|
90
81
|
}
|
|
@@ -95,7 +86,7 @@ Node* Node::my_next_inner(Node *last, int versus) {
|
|
|
95
86
|
else last_node_index = this->down_indexer - last->abs_x_index;
|
|
96
87
|
if (versus == Node::O) last_node_index == 0 ? last_node_index = this->tangs_sequence.size() - 1 : last_node_index--;
|
|
97
88
|
else last_node_index == this->tangs_sequence.size() - 1 ? last_node_index = 0 : last_node_index++;
|
|
98
|
-
return(
|
|
89
|
+
return get_tangent_node_by_virtual_index(this->tangs_sequence[last_node_index]);
|
|
99
90
|
}
|
|
100
91
|
|
|
101
92
|
Node* Node::my_next_outer(Node *last, int versus) {
|
|
@@ -104,15 +95,42 @@ Node* Node::my_next_outer(Node *last, int versus) {
|
|
|
104
95
|
else last_node_index = this->down_indexer - last->abs_x_index;
|
|
105
96
|
if (versus == Node::O) last_node_index == this->tangs_sequence.size() - 1 ? last_node_index = 0 : last_node_index++;
|
|
106
97
|
else last_node_index == 0 ? last_node_index = this->tangs_sequence.size() - 1 : last_node_index--;
|
|
107
|
-
return(
|
|
98
|
+
return get_tangent_node_by_virtual_index(this->tangs_sequence[last_node_index]);
|
|
108
99
|
}
|
|
109
100
|
|
|
110
101
|
Point* Node::coords_entering_to(Node *enter_to, int mode, int tracking) {
|
|
111
102
|
int enter_to_index;
|
|
112
103
|
if (enter_to->y < this->y) enter_to_index = enter_to->abs_x_index + this->up_indexer;
|
|
113
104
|
else enter_to_index = this->down_indexer - enter_to->abs_x_index;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
105
|
+
|
|
106
|
+
int tg_index = this->tangs_sequence[enter_to_index];
|
|
107
|
+
Point* point;
|
|
108
|
+
if (tg_index < 0) {
|
|
109
|
+
Node& node_up = cluster->vert_nodes[y + T_UP][-(tg_index + 1)];
|
|
110
|
+
if (mode == Node::A) {
|
|
111
|
+
enter_to->track |= TURNER[tracking][OMAX - 1];
|
|
112
|
+
point = &node_up.end_point;
|
|
113
|
+
} else {
|
|
114
|
+
enter_to->track |= TURNER[tracking][OMIN - 1];
|
|
115
|
+
point = &node_up.start_point;
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
Node& node_down = cluster->vert_nodes[y + T_DOWN][tg_index];
|
|
119
|
+
if (mode == Node::A) {
|
|
120
|
+
enter_to->track |= TURNER[tracking][OMIN - 1];
|
|
121
|
+
point = &node_down.start_point;
|
|
122
|
+
} else {
|
|
123
|
+
enter_to->track |= TURNER[tracking][OMAX - 1];
|
|
124
|
+
point = &node_down.end_point;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return point;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
Node* Node::get_tangent_node_by_virtual_index(int virtual_index) {
|
|
131
|
+
if (virtual_index < 0) {
|
|
132
|
+
return &(this->cluster->vert_nodes[y + T_UP][-(virtual_index + 1)]);
|
|
133
|
+
} else {
|
|
134
|
+
return &(this->cluster->vert_nodes[y + T_DOWN][virtual_index]);
|
|
135
|
+
}
|
|
118
136
|
}
|
|
@@ -28,11 +28,6 @@ struct Point {
|
|
|
28
28
|
}
|
|
29
29
|
Point(int x_, int y_) : x(x_), y(y_) {}
|
|
30
30
|
};
|
|
31
|
-
struct Tangent {
|
|
32
|
-
Point *point;
|
|
33
|
-
int mode;
|
|
34
|
-
};
|
|
35
|
-
struct NodeDescriptor;
|
|
36
31
|
|
|
37
32
|
class Node : public Listable {
|
|
38
33
|
public:
|
|
@@ -63,24 +58,20 @@ class Node : public Listable {
|
|
|
63
58
|
int lower_start = std::numeric_limits<int>::max();
|
|
64
59
|
int lower_end = -1;
|
|
65
60
|
Point start_point, end_point;
|
|
61
|
+
NodeCluster* cluster;
|
|
66
62
|
void add_intersection(Node& other_node, int other_node_index);
|
|
67
|
-
std::vector<
|
|
63
|
+
std::vector<int> tangs_sequence;
|
|
68
64
|
Point* coords_entering_to(Node *enter_to, int mode, int tracking);
|
|
69
65
|
Node* my_next_outer(Node *last, int versus);
|
|
70
66
|
Node* my_next_inner(Node *last, int versus);
|
|
67
|
+
Node* get_tangent_node_by_virtual_index(int vitual_index);
|
|
71
68
|
bool track_uncomplete();
|
|
72
69
|
bool track_complete();
|
|
73
70
|
bool get_trackmax();
|
|
74
71
|
|
|
75
72
|
public:
|
|
76
73
|
int min_x, max_x;
|
|
77
|
-
Node(int min_x, int max_x, int y, char name);
|
|
74
|
+
Node(int min_x, int max_x, int y, NodeCluster* cluster, char name);
|
|
78
75
|
void precalc_tangs_sequences(NodeCluster& cluster);
|
|
79
76
|
bool processed = false;
|
|
80
77
|
};
|
|
81
|
-
|
|
82
|
-
struct NodeDescriptor {
|
|
83
|
-
Node *node;
|
|
84
|
-
Tangent a;
|
|
85
|
-
Tangent o;
|
|
86
|
-
};
|