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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -1
  3. data/Gemfile.lock +1 -1
  4. data/README.md +37 -18
  5. data/ext/cpp_polygon_finder/PolygonFinder/examples/example.cpp +3 -1
  6. data/ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h +1 -1
  7. data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +71 -2
  8. data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.h +2 -0
  9. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.cpp +13 -47
  10. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.h +2 -11
  11. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.cpp +64 -0
  12. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.h +37 -0
  13. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RemoteFastPngBitmap.cpp +2 -4
  14. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +38 -20
  15. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +4 -13
  16. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +15 -5
  17. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +2 -0
  18. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +12 -0
  19. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +3 -1
  20. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/EndPoint.h +3 -2
  21. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +10 -2
  22. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.h +8 -4
  23. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.cpp +22 -0
  24. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.h +19 -0
  25. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.cpp +46 -0
  26. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.h +24 -0
  27. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +2 -2
  28. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +4 -4
  29. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +3 -2
  30. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Position.cpp +2 -5
  31. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queue.h +5 -0
  32. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.h +1 -1
  33. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.cpp +64 -0
  34. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.h +24 -0
  35. data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +91 -0
  36. data/lib/contrek/bitmaps/{custom_bitmap.rb → raw_bitmap.rb} +1 -1
  37. data/lib/contrek/cpp/cpp_concurrent_horizontal_merger.rb +9 -0
  38. data/lib/contrek/cpp/cpp_concurrent_merger.rb +9 -0
  39. data/lib/contrek/cpp/cpp_concurrent_vertical_merger.rb +9 -0
  40. data/lib/contrek/finder/concurrent/cursor.rb +2 -0
  41. data/lib/contrek/finder/concurrent/finder.rb +8 -5
  42. data/lib/contrek/finder/concurrent/horizontal_merger.rb +10 -0
  43. data/lib/contrek/finder/concurrent/merger.rb +52 -0
  44. data/lib/contrek/finder/concurrent/tile.rb +0 -2
  45. data/lib/contrek/finder/concurrent/vertical_merger.rb +44 -0
  46. data/lib/contrek/finder/node.rb +38 -21
  47. data/lib/contrek/finder/node_cluster.rb +12 -15
  48. data/lib/contrek/finder/polygon_finder.rb +2 -1
  49. data/lib/contrek/reducers/linear_reducer.rb +1 -1
  50. data/lib/contrek/version.rb +1 -1
  51. data/lib/contrek.rb +7 -1
  52. metadata +20 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60fae59ed14c24cedd7dadfdc33dea05188b1131ee6443b59591e5243cb4710b
4
- data.tar.gz: b565f7a0e9713a9389329cf1cc44f9c2da36908d75c538af625afdb6e1d9d71a
3
+ metadata.gz: 22d05e676f5c4a0f22f02ec2e915257bf1f5da224d468cb0e2f46fc397360242
4
+ data.tar.gz: '0997b63d2839cc3ba613884eff2e3a565d96502e4606db0bb72d2aee228e514d'
5
5
  SHA512:
6
- metadata.gz: 86ece9b65041fae9aec64173f5e3bb719971852a6327953b78dc6567f94018db544cc8b7e062d57aad3a859f8142982406e1d9301716f2a49eaa8a05a586ddfc
7
- data.tar.gz: 87a22df67016a3249a069cec0bee1746ddb92c1103f9a98e0a8e85b2240ae7d4be735123cfa72443432b30a91be333c4d195b384b194f5703a075b4832e07bd6
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contrek (1.1.3)
4
+ contrek (1.1.5)
5
5
  chunky_png (~> 1.4)
6
6
  concurrent-ruby (~> 1.3.5)
7
7
  rice (= 4.5.0)
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
  ![alt text](contrek.png "Contour tracing")
@@ -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.53 secs with image load).
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: 13.0815,
130
- init: 453.245,
131
- inner: 27.0072,
132
- outer: 66.9162,
133
- total: 466.326
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=>801.494,
292
- :build_tangs_sequence=>160.491,
293
- :plot=>86.633,
309
+ { :scan=>775.435,
310
+ :build_tangs_sequence=>38.916,
311
+ :plot=>101.876,
294
312
  :compress=>0.002,
295
- :total=>1048.62}
313
+ :total=>916.229
314
+ }
296
315
  ```
297
316
  This the one for the native C++
298
317
  ```ruby
299
- { scan: 7.1146329999999995,
300
- build_tangs_sequence: 3.063812,
301
- plot: 4.474851999999999,
302
- compress: 0.0031999999999999997
303
- total: 14.656496999999998
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 75x faster. Times are in microseconds; system: AMD Ryzen 7 3700X 8-Core Processor (BogoMIPS: 7199,99) on Ubuntu distro.
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 << "immagine =" << cpu_timer.stop() << std::endl;
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, &not_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, &not_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
+ }
@@ -17,4 +17,6 @@ class Tests {
17
17
  virtual void test_d();
18
18
  virtual void test_e();
19
19
  virtual void test_f();
20
+ virtual void test_g();
21
+ virtual void test_h();
20
22
  };
@@ -20,72 +20,38 @@
20
20
  #include "FastPngBitmap.h"
21
21
  #include "spng.h"
22
22
 
23
- FastPngBitmap::FastPngBitmap(std::string filename) : Bitmap("", 0) {
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();
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
- { this->png_error = -1;
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->image.resize(out_size);
42
- madvise(this->image.data(), out_size, MADV_HUGEPAGE);
43
- int error = spng_decode_image(ctx, image.data(), out_size, SPNG_FMT_RGBA8, SPNG_DECODE_TRNS);
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 "Bitmap.h"
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 Bitmap {
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->image.resize(out_size);
38
- error = spng_decode_image(ctx, image.data(), out_size, SPNG_FMT_RGBA8, SPNG_DECODE_TRNS);
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
- Node& t_node = cluster.vert_nodes[y + T_UP][upper_pos];
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
- Node& t_node = cluster.vert_nodes[y + T_DOWN][lower_pos];
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((this->tangs_sequence)[last_node_index].node);
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((this->tangs_sequence)[last_node_index].node);
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
- NodeDescriptor ds = (this->tangs_sequence)[enter_to_index];
115
- Tangent t = (mode == Node::O ? ds.o : ds.a);
116
- enter_to->track |= TURNER[tracking][t.mode - 1];
117
- return(t.point);
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<NodeDescriptor> tangs_sequence;
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
- };