contrek 1.1.4 → 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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +20 -2
  5. data/ext/cpp_polygon_finder/PolygonFinder/examples/example.cpp +3 -1
  6. data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +71 -2
  7. data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.h +2 -0
  8. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.cpp +12 -48
  9. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.h +2 -11
  10. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.cpp +64 -0
  11. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.h +37 -0
  12. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RemoteFastPngBitmap.cpp +2 -4
  13. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +2 -0
  14. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +12 -0
  15. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +10 -2
  16. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.h +8 -4
  17. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.cpp +22 -0
  18. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.h +19 -0
  19. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.cpp +46 -0
  20. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.h +24 -0
  21. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queue.h +5 -0
  22. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.h +1 -1
  23. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.cpp +64 -0
  24. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.h +24 -0
  25. data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +91 -0
  26. data/lib/contrek/bitmaps/{custom_bitmap.rb → raw_bitmap.rb} +1 -1
  27. data/lib/contrek/cpp/cpp_concurrent_horizontal_merger.rb +9 -0
  28. data/lib/contrek/cpp/cpp_concurrent_merger.rb +9 -0
  29. data/lib/contrek/cpp/cpp_concurrent_vertical_merger.rb +9 -0
  30. data/lib/contrek/finder/concurrent/finder.rb +8 -5
  31. data/lib/contrek/finder/concurrent/horizontal_merger.rb +10 -0
  32. data/lib/contrek/finder/concurrent/merger.rb +52 -0
  33. data/lib/contrek/finder/concurrent/tile.rb +0 -2
  34. data/lib/contrek/finder/concurrent/vertical_merger.rb +44 -0
  35. data/lib/contrek/finder/polygon_finder.rb +2 -0
  36. data/lib/contrek/reducers/linear_reducer.rb +1 -1
  37. data/lib/contrek/version.rb +1 -1
  38. data/lib/contrek.rb +7 -1
  39. metadata +17 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b2690fa75c9a6671f9c8ea91522d3adaf8600bfc0739dad4c8eb4a14b80d97e
4
- data.tar.gz: 38db9719cc44356ec32d33031e877edda131a970ec0fb5e385f9c31e19699858
3
+ metadata.gz: 22d05e676f5c4a0f22f02ec2e915257bf1f5da224d468cb0e2f46fc397360242
4
+ data.tar.gz: '0997b63d2839cc3ba613884eff2e3a565d96502e4606db0bb72d2aee228e514d'
5
5
  SHA512:
6
- metadata.gz: 4827a05d797400b0245c8225274dc070870e83186d51da4b4f16bb8368359597967012b65cedbff39f808fa9d1eafad5e3012e144f10afb801d564a2ff916dca
7
- data.tar.gz: 690435e9531d0c8c47c703e41c4719333b91edbe60232a5183c30e21a2822c06b827498b49afbda4785cff404e3abdaf776f0a0b4aec9c9b58a0a9fbf29c8844
6
+ metadata.gz: 57bf5e08937ea8fb4cee39e383a8f760e3a36714643c583408af6d02c7fbb84515af8d3089c753319677b8d5223c7a6a07f505c4356e21cd10570abbec358210
7
+ data.tar.gz: 0ec4babf59251515b05857a459adfd7365b930feaf58b17ca2215ab4b534abe3ac41ae45e7643270ec8acba4c699fbcd311b12eb3ddab6789e5dd1b7275ad220
data/CHANGELOG.md CHANGED
@@ -58,3 +58,8 @@ All notable changes to this project will be documented in this file.
58
58
  ### Changed
59
59
  - Fixed an infinite loop bug in multithreading during inner sequence joining in Omnidirectional mode.
60
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.4)
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
@@ -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
 
@@ -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,74 +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
  }
47
46
  } else {
48
- throw std::runtime_error("Unable open file: " + filename);
47
+ throw std::runtime_error("Unable to open file: " + filename);
49
48
  }
50
49
  }
51
50
  }
52
51
 
53
- bool FastPngBitmap::pixel_match(int x, int y, Matcher *matcher)
54
- { int32_t index = ((y * width) + x) * 4;
55
- unsigned int color;
56
- unsigned char *red = &image[index];
57
- std::memcpy(&color, red, 3);
58
- if (typeid(*matcher) == typeid(RGBMatcher)) return((reinterpret_cast<RGBMatcher*>(matcher))->match(color));
59
- if (typeid(*matcher) == typeid(RGBNotMatcher)) return((reinterpret_cast<RGBNotMatcher*>(matcher))->match(color));
60
- return(false);
61
- }
62
-
63
- int FastPngBitmap::w() {
64
- return this->width;
65
- }
66
-
67
- int FastPngBitmap::h() {
68
- return this->height;
69
- }
70
-
71
52
  int FastPngBitmap::error() {
72
53
  return this->png_error;
73
54
  }
74
- char FastPngBitmap::value_at(int x, int y) {
75
- return(0);
76
- }
77
-
78
- // source image format RGBA => returning uint ABGR
79
- unsigned int FastPngBitmap::rgb_value_at(int x, int y) {
80
- uint32_t index = (uint32_t(y) * width + x) * 4;
81
- return *reinterpret_cast<const uint32_t*>(&image[index]);
82
- }
83
-
84
- const unsigned char* FastPngBitmap::get_row_ptr(int y) const {
85
- return image.data() + (static_cast<size_t>(y) * static_cast<size_t>(width) * 4);
86
- }
87
-
88
- int FastPngBitmap::get_bytes_per_pixel() const {
89
- return 4; // RGBA
90
- }
91
55
 
92
56
  void FastPngBitmap::loadFile(std::vector<unsigned char>& buffer, const std::string& filename) // designed for loading files from hard disk in an std::vector
93
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;
@@ -104,6 +104,8 @@ ProcessResult* PolygonFinder::process_info() {
104
104
  pr->polygons = std::move(this->node_cluster->polygons);
105
105
  pr->benchmarks = std::move(this->reports);
106
106
  pr->treemap = this->node_cluster->treemap;
107
+ pr->width = this->source_bitmap->w();
108
+ pr->height = this->source_bitmap->h();
107
109
 
108
110
  if (this->node_cluster->options->named_sequences && typeid(*this->source_bitmap) == typeid(Bitmap))
109
111
  { std::string sequence;
@@ -50,6 +50,7 @@ struct pf_Options {
50
50
  };
51
51
  struct ProcessResult {
52
52
  int groups;
53
+ int width, height;
53
54
  std::map<std::string, double> benchmarks;
54
55
  std::list<Polygon> polygons;
55
56
  std::string named_sequence;
@@ -77,6 +78,17 @@ struct ProcessResult {
77
78
  }
78
79
  std::cout << std::endl;
79
80
  }
81
+
82
+ void translate(int x) {
83
+ for (auto& polygon : polygons) {
84
+ for (Point* p : polygon.outer) p->x += x;
85
+ for (const auto& seq : polygon.inner) {
86
+ for (Point* p : seq) p->x += x;
87
+ }
88
+ polygon.bounds.min_x += x;
89
+ polygon.bounds.max_x += x;
90
+ }
91
+ }
80
92
  };
81
93
 
82
94
  class PolygonFinder {
@@ -27,7 +27,8 @@ Finder::Finder(int number_of_threads, Bitmap *bitmap, Matcher *matcher, std::vec
27
27
  bitmap(bitmap),
28
28
  matcher(matcher),
29
29
  input_options(*options),
30
- maximum_width_(bitmap->w())
30
+ maximum_width_(bitmap->w()),
31
+ height(bitmap->h())
31
32
  { cpu_timer.start();
32
33
  if (options != nullptr) FinderUtils::sanitize_options(this->options, options);
33
34
 
@@ -67,6 +68,11 @@ Finder::Finder(int number_of_threads, Bitmap *bitmap, Matcher *matcher, std::vec
67
68
  reports["init"] = cpu_timer.stop();
68
69
  }
69
70
 
71
+ Finder::Finder(int number_of_threads, std::vector<std::string> *options)
72
+ : Poolable(number_of_threads), bitmap(nullptr), matcher(nullptr), input_options(*options), maximum_width_(0) {
73
+ if (options != nullptr) FinderUtils::sanitize_options(this->options, options);
74
+ reports["init"] = 0;
75
+ }
70
76
 
71
77
  void Finder::process_tiles() {
72
78
  std::vector<Tile*> arriving_tiles;
@@ -97,7 +103,7 @@ void Finder::process_tiles() {
97
103
  end_x = tile->end_x();
98
104
  }
99
105
 
100
- Cluster *cluster = new Cluster(this, this->bitmap->h(), start_x, end_x);
106
+ Cluster *cluster = new Cluster(this, this->height, start_x, end_x);
101
107
 
102
108
  if (twin_tile->start_x() == (tile->end_x() - 1)) {
103
109
  cluster->add(tile);
@@ -137,6 +143,8 @@ ProcessResult* Finder::process_info() {
137
143
  ProcessResult *pr = new ProcessResult();
138
144
  pr->polygons = std::move(this->whole_tile->to_raw_polygons());
139
145
  pr->groups = pr->polygons.size();
146
+ pr->width = this->maximum_width_;
147
+ pr->height = this->height;
140
148
  FakeCluster fake_cluster(pr->polygons, this->options);
141
149
  cpu_timer.start();
142
150
  fake_cluster.compress_coords(pr->polygons, this->options);
@@ -35,19 +35,23 @@ class Finder : public Poolable {
35
35
  Matcher *matcher;
36
36
  pf_Options options;
37
37
  std::vector<std::string> input_options;
38
- int maximum_width_;
39
- Queue<Tile*> tiles;
40
38
  Tile* whole_tile = nullptr;
41
- void process_tiles();
42
39
  std::queue<ClippedPolygonFinder*> finders;
43
40
  std::mutex finders_mutex;
44
41
  std::map<std::string, double> reports;
45
42
  CpuTimer cpu_timer;
46
43
 
44
+ protected:
45
+ Queue<Tile*> tiles;
46
+ int maximum_width_;
47
+ int height = 0;
48
+ void process_tiles();
49
+
47
50
  public:
48
51
  using Poolable::Poolable;
49
52
  Finder(int number_of_threads, Bitmap *bitmap, Matcher *matcher, std::vector<std::string> *options);
53
+ Finder(int number_of_threads, std::vector<std::string> *options);
50
54
  virtual ~Finder();
51
55
  int maximum_width() const { return maximum_width_; }
52
- ProcessResult* process_info();
56
+ virtual ProcessResult* process_info();
53
57
  };
@@ -0,0 +1,22 @@
1
+ /*
2
+ * HorizontalMerger.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 <iostream>
11
+ #include <string>
12
+ #include <vector>
13
+ #include "HorizontalMerger.h"
14
+
15
+ HorizontalMerger::HorizontalMerger(int number_of_threads, std::vector<std::string> *options)
16
+ : Merger(number_of_threads, options) {
17
+ }
18
+
19
+ void HorizontalMerger::add_tile(ProcessResult& result)
20
+ { translate(result, this->current_x);
21
+ Merger::add_tile(result);
22
+ }
@@ -0,0 +1,19 @@
1
+ /*
2
+ * HorizontalMerger.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 <string>
12
+ #include <vector>
13
+ #include "Merger.h"
14
+
15
+ class HorizontalMerger : public Merger {
16
+ public:
17
+ HorizontalMerger(int number_of_threads, std::vector<std::string> *options);
18
+ void add_tile(ProcessResult& result) override;
19
+ };
@@ -0,0 +1,46 @@
1
+ /*
2
+ * Merger.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 <iostream>
11
+ #include <string>
12
+ #include <vector>
13
+ #include "Merger.h"
14
+
15
+ Merger::Merger(int number_of_threads, std::vector<std::string> *options)
16
+ : Finder(number_of_threads, options) {
17
+ }
18
+
19
+ void Merger::add_tile(ProcessResult& result)
20
+ { if (this->height == 0) {
21
+ this->height = result.height;
22
+ }
23
+ int end_x = this->current_x + result.width;
24
+ Tile* tile = new Tile(this, this->current_x, end_x, std::to_string(tiles.size()), Benchmarks {0, 0});
25
+ tile->assign_raw_polygons(result.polygons);
26
+ tiles.queue_push(tile);
27
+
28
+ this->maximum_width_ = end_x;
29
+ this->current_x = end_x - 1;
30
+ }
31
+
32
+ ProcessResult* Merger::process_info() {
33
+ this->process_tiles();
34
+ return(Finder::process_info());
35
+ }
36
+
37
+ void Merger::translate(ProcessResult& result, int offset) {
38
+ for (auto& polygon : result.polygons) {
39
+ for (Point* p : polygon.outer) p->x += offset;
40
+ for (const auto& seq : polygon.inner) {
41
+ for (Point* p : seq) p->x += offset;
42
+ }
43
+ polygon.bounds.min_x += offset;
44
+ polygon.bounds.max_x += offset;
45
+ }
46
+ }
@@ -0,0 +1,24 @@
1
+ /*
2
+ * Merger.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 <string>
12
+ #include <vector>
13
+ #include "Finder.h"
14
+
15
+ class Merger : public Finder {
16
+ public:
17
+ Merger(int number_of_threads, std::vector<std::string> *options);
18
+ virtual void add_tile(ProcessResult& result);
19
+ ProcessResult* process_info() override;
20
+
21
+ protected:
22
+ void translate(ProcessResult& result, int offset);
23
+ int current_x = 0;
24
+ };
@@ -20,6 +20,7 @@ class Queue {
20
20
  std::lock_guard<std::mutex> lock(mutex_);
21
21
  queue_.push(value);
22
22
  cond_.notify_one();
23
+ this->size_++;
23
24
  }
24
25
 
25
26
  T queue_pop() {
@@ -27,10 +28,14 @@ class Queue {
27
28
  cond_.wait(lock, [this]{ return !queue_.empty(); });
28
29
  T value = queue_.front();
29
30
  queue_.pop();
31
+ this->size_--;
30
32
  return value;
31
33
  }
32
34
 
35
+ int size() { return this->size_; }
36
+
33
37
  private:
38
+ int size_ = 0;
34
39
  std::queue<T> queue_;
35
40
  std::mutex mutex_;
36
41
  std::condition_variable cond_;
@@ -30,7 +30,6 @@ class Tile {
30
30
  std::list<Shape*> shapes_;
31
31
  std::list<Shape*> boundary_shapes_;
32
32
  bool boundary_shapes_initialized_ = false;
33
- void assign_raw_polygons(const std::list<Polygon>& raw_polylines);
34
33
 
35
34
  public:
36
35
  Tile(Finder *finder, int start_x, int end_x, std::string name, const Benchmarks& b);
@@ -52,6 +51,7 @@ class Tile {
52
51
  void info();
53
52
  bool tg_border(const Point& coord);
54
53
  void assign_shapes(std::list<Shape*>& shapes);
54
+ void assign_raw_polygons(const std::list<Polygon>& raw_polylines);
55
55
  std::list<Polygon> to_raw_polygons();
56
56
  Benchmarks benchmarks;
57
57
  };
@@ -0,0 +1,64 @@
1
+ /*
2
+ * VerticalMerger.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 <iostream>
11
+ #include <string>
12
+ #include <vector>
13
+ #include <algorithm>
14
+ #include <utility>
15
+ #include "VerticalMerger.h"
16
+
17
+ VerticalMerger::VerticalMerger(int number_of_threads, std::vector<std::string> *options)
18
+ : Merger(number_of_threads, options) {
19
+ }
20
+
21
+ void VerticalMerger::add_tile(ProcessResult& result)
22
+ { transpose(result);
23
+ if (this->tiles.size() > 0) {
24
+ translate(result, this->current_x);
25
+ adjust(result);
26
+ }
27
+ Merger::add_tile(result);
28
+ }
29
+
30
+ ProcessResult* VerticalMerger::process_info() {
31
+ ProcessResult* result = Merger::process_info();
32
+ transpose(*result);
33
+ return(result);
34
+ }
35
+
36
+ void VerticalMerger::transpose(ProcessResult& result) {
37
+ std::swap(result.width, result.height);
38
+
39
+ for (auto& polygon : result.polygons) {
40
+ for (Point* p : polygon.outer) {
41
+ if (p) std::swap(p->x, p->y);
42
+ }
43
+ for (auto& sequence : polygon.inner) {
44
+ for (Point* p : sequence) {
45
+ if (p) std::swap(p->x, p->y);
46
+ }
47
+ }
48
+ std::swap(polygon.bounds.min_x, polygon.bounds.min_y);
49
+ std::swap(polygon.bounds.max_x, polygon.bounds.max_y);
50
+ }
51
+ }
52
+
53
+ void VerticalMerger::adjust(ProcessResult& result) {
54
+ for (auto& polygon : result.polygons) {
55
+ if (!polygon.outer.empty()) {
56
+ std::rotate(polygon.outer.begin(), polygon.outer.begin() + 1, polygon.outer.end());
57
+ }
58
+ for (auto& sequence : polygon.inner) {
59
+ if (!sequence.empty()) {
60
+ std::rotate(sequence.begin(), sequence.begin() + 1, sequence.end());
61
+ }
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,24 @@
1
+ /*
2
+ * VerticalMerger.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 <string>
12
+ #include <vector>
13
+ #include "Merger.h"
14
+
15
+ class VerticalMerger : public Merger {
16
+ public:
17
+ VerticalMerger(int number_of_threads, std::vector<std::string> *options);
18
+ void add_tile(ProcessResult& result) override;
19
+ ProcessResult* process_info() override;
20
+
21
+ private:
22
+ void transpose(ProcessResult& result);
23
+ void adjust(ProcessResult& result);
24
+ };
@@ -27,6 +27,8 @@
27
27
  #include "PolygonFinder/src/polygon/finder/Polygon.h"
28
28
  #include "PolygonFinder/src/polygon/bitmaps/Bitmap.h"
29
29
  #include "PolygonFinder/src/polygon/bitmaps/Bitmap.cpp"
30
+ #include "PolygonFinder/src/polygon/bitmaps/RawBitmap.h"
31
+ #include "PolygonFinder/src/polygon/bitmaps/RawBitmap.cpp"
30
32
  #include "PolygonFinder/src/polygon/bitmaps/FastPngBitmap.h"
31
33
  #include "PolygonFinder/src/polygon/bitmaps/FastPngBitmap.cpp"
32
34
  #include "PolygonFinder/src/polygon/bitmaps/RemoteFastPngBitmap.h"
@@ -81,6 +83,12 @@
81
83
  #include "PolygonFinder/src/polygon/finder/concurrent/Sequence.cpp"
82
84
  #include "PolygonFinder/src/polygon/finder/concurrent/PartPool.h"
83
85
  #include "PolygonFinder/src/polygon/finder/concurrent/PartPool.cpp"
86
+ #include "PolygonFinder/src/polygon/finder/concurrent/Merger.h"
87
+ #include "PolygonFinder/src/polygon/finder/concurrent/Merger.cpp"
88
+ #include "PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.h"
89
+ #include "PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.cpp"
90
+ #include "PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.h"
91
+ #include "PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.cpp"
84
92
  extern "C" {
85
93
  #include "PolygonFinder/src/polygon/bitmaps/spng.h"
86
94
  }
@@ -164,6 +172,8 @@ class To_Ruby<ProcessResult*>
164
172
  return_me[Symbol("benchmarks")] = benchmarks_rb;
165
173
  return_me[Symbol("groups")] = pr->groups;
166
174
  return_me[Symbol("named_sequence")] = pr->named_sequence;
175
+ return_me[Symbol("width")] = pr->width;
176
+ return_me[Symbol("height")] = pr->height;
167
177
 
168
178
  Rice::Array out;
169
179
  for (Polygon& x : pr->polygons)
@@ -186,6 +196,14 @@ class To_Ruby<ProcessResult*>
186
196
  inner_collection.push(sequence_flat);
187
197
  }
188
198
  poly_hash[Symbol("inner")] = inner_collection;
199
+ // BOUNDS
200
+ Rice::Hash bounds_hash = Rice::Hash();
201
+ bounds_hash[Symbol("min_x")] = x.bounds.min_x;
202
+ bounds_hash[Symbol("max_x")] = x.bounds.max_x;
203
+ bounds_hash[Symbol("min_y")] = x.bounds.min_y;
204
+ bounds_hash[Symbol("max_y")] = x.bounds.max_y;
205
+ poly_hash[Symbol("bounds")] = bounds_hash;
206
+
189
207
  out.push(poly_hash);
190
208
  }
191
209
  rr->polygons = out;
@@ -212,6 +230,53 @@ class To_Ruby<ProcessResult*>
212
230
  }
213
231
  };
214
232
 
233
+ // converts back ProcessResult
234
+ ProcessResult ruby_result_to_process_result(Rice::Object rb_result) {
235
+ ProcessResult pr;
236
+
237
+ Rice::Hash metadata = rb_result.iv_get("@metadata_storage");
238
+ pr.width = detail::From_Ruby<int>().convert(metadata[Symbol("width")].value());
239
+ pr.height = detail::From_Ruby<int>().convert(metadata[Symbol("height")].value());
240
+
241
+ Rice::Array rb_polygons = rb_result.iv_get("@polygons_storage");
242
+ for (size_t i = 0; i < rb_polygons.size(); ++i) {
243
+ // Cast esplicito a Rice::Hash per evitare il Proxy
244
+ Rice::Hash rb_poly = (Rice::Hash)rb_polygons[i];
245
+ Polygon poly;
246
+ // BOUNDS
247
+ Rice::Object rb_bounds_obj = rb_poly[Symbol("bounds")];
248
+ if (!rb_bounds_obj.is_nil()) {
249
+ Rice::Hash rb_bounds = (Rice::Hash)rb_bounds_obj;
250
+ poly.bounds.min_x = detail::From_Ruby<int>().convert(rb_bounds[Symbol("min_x")].value());
251
+ poly.bounds.max_x = detail::From_Ruby<int>().convert(rb_bounds[Symbol("max_x")].value());
252
+ poly.bounds.min_y = detail::From_Ruby<int>().convert(rb_bounds[Symbol("min_y")].value());
253
+ poly.bounds.max_y = detail::From_Ruby<int>().convert(rb_bounds[Symbol("max_y")].value());
254
+ }
255
+ // OUTER
256
+ Rice::Array outer_flat = (Rice::Array)rb_poly[Symbol("outer")];
257
+ for (size_t j = 0; j < outer_flat.size(); j += 2) {
258
+ int px = detail::From_Ruby<int>().convert(outer_flat[j].value());
259
+ int py = detail::From_Ruby<int>().convert(outer_flat[j+1].value());
260
+ poly.outer.push_back(new Point(px, py));
261
+ }
262
+ // INNER
263
+ Rice::Array inner_collection = (Rice::Array)rb_poly[Symbol("inner")];
264
+ for (size_t j = 0; j < inner_collection.size(); ++j) {
265
+ Rice::Array sequence_flat = (Rice::Array)inner_collection[j];
266
+ std::vector<Point*> hole;
267
+ for (size_t k = 0; k < sequence_flat.size(); k += 2) {
268
+ int px = detail::From_Ruby<int>().convert(sequence_flat[k].value());
269
+ int py = detail::From_Ruby<int>().convert(sequence_flat[k+1].value());
270
+ hole.push_back(new Point(px, py));
271
+ }
272
+ poly.inner.push_back(hole);
273
+ }
274
+ pr.polygons.push_back(poly);
275
+ }
276
+ return pr;
277
+ }
278
+
279
+
215
280
  } // namespace Rice::detail
216
281
 
217
282
  extern "C"
@@ -227,6 +292,15 @@ void Init_cpp_polygon_finder() {
227
292
  .define_method("clear", &Bitmap::clear)
228
293
  .define_method("print", &Bitmap::print);
229
294
 
295
+ Data_Type<RawBitmap> rb_cRawBitmap =
296
+ define_class<RawBitmap, Bitmap>("CPPRawBitMap")
297
+ .define_constructor(Constructor<RawBitmap>())
298
+ .define_method("rgb_value_at", &RawBitmap::rgb_value_at)
299
+ .define_method("w", &RawBitmap::w)
300
+ .define_method("h", &RawBitmap::h)
301
+ .define_method("define", &RawBitmap::define)
302
+ .define_method("draw_pixel", &RawBitmap::draw_pixel);
303
+
230
304
  Data_Type<RemoteFastPngBitmap> rb_cRemotePngBitmap =
231
305
  define_class<RemoteFastPngBitmap, Bitmap>("CPPRemotePngBitMap")
232
306
  .define_constructor(Constructor<RemoteFastPngBitmap, std::string*>(), Arg("url"))
@@ -280,6 +354,23 @@ void Init_cpp_polygon_finder() {
280
354
  .define_constructor(Constructor<Finder, int, Bitmap*, Matcher*, std::vector<std::string>*>(), Arg("number_of_threads"), Arg("bitmap"), Arg("matcher"), Arg("options") = nullptr, Rice::Arg("yield_gvl") = true)
281
355
  .define_method("process_info", &Finder::process_info, Rice::Arg("yield_gvl") = true);
282
356
 
357
+ Data_Type<Merger> rb_cMerger =
358
+ define_class<Merger, Finder>("CPPMerger")
359
+ .define_constructor(Constructor<Merger, int, std::vector<std::string>*>(), Arg("number_of_threads"), Arg("options") = nullptr, Rice::Arg("yield_gvl") = true)
360
+ .define_method("add_tile", [](Merger& self, Object rb_result) {
361
+ ProcessResult pr = Rice::detail::ruby_result_to_process_result(rb_result);
362
+ self.add_tile(pr);
363
+ })
364
+ .define_method("process_info", &Merger::process_info, Rice::Arg("yield_gvl") = true);
365
+
366
+ Data_Type<HorizontalMerger> rb_cHorizontalMerger =
367
+ define_class<HorizontalMerger, Merger>("CPPHorizontalMerger")
368
+ .define_constructor(Constructor<HorizontalMerger, int, std::vector<std::string>*>(), Arg("number_of_threads"), Arg("options") = nullptr, Arg("yield_gvl") = true);
369
+
370
+ Data_Type<VerticalMerger> rb_cVerticalMerger =
371
+ define_class<VerticalMerger, Merger>("CPPVerticalMerger")
372
+ .define_constructor(Constructor<VerticalMerger, int, std::vector<std::string>*>(), Arg("number_of_threads"), Arg("options") = nullptr, Arg("yield_gvl") = true);
373
+
283
374
  Data_Type<RubyResult> rb_cResult =
284
375
  define_class_under<RubyResult>(rb_cFinder, "Result")
285
376
  .define_constructor(Constructor<RubyResult>())
@@ -1,6 +1,6 @@
1
1
  module Contrek
2
2
  module Bitmaps
3
- class CustomBitmap < PngBitmap
3
+ class RawBitmap < PngBitmap
4
4
  def initialize(w:, h:, color: ChunkyPNG::Color::TRANSPARENT)
5
5
  @image = ChunkyPNG::Image.new(w, h, color)
6
6
  end
@@ -0,0 +1,9 @@
1
+ module Contrek
2
+ module Cpp
3
+ class CPPConcurrentHorizontalMerger < CPPHorizontalMerger
4
+ def initialize(number_of_threads: 0, options: nil)
5
+ super(number_of_threads, options)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Contrek
2
+ module Cpp
3
+ class CPPConcurrentMerger < CPPMerger
4
+ def initialize(number_of_threads: 0, options: nil)
5
+ super(number_of_threads, options)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Contrek
2
+ module Cpp
3
+ class CPPConcurrentVerticalMerger < CPPVerticalMerger
4
+ def initialize(number_of_threads: 0, options: nil)
5
+ super(number_of_threads, options)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -28,6 +28,7 @@ module Contrek
28
28
  @options = options
29
29
  @clusters = []
30
30
  @maximum_width = bitmap.w
31
+ @height = bitmap.h
31
32
  @number_of_tiles = options[:number_of_tiles] || (raise "number_of_tiles params is needed!")
32
33
  @number_of_tiles = 1 if @number_of_tiles <= 0
33
34
 
@@ -62,11 +63,11 @@ module Contrek
62
63
 
63
64
  x = tile_end_x - 1
64
65
  end
65
- process_tiles!(bitmap)
66
+ process_tiles!(bitmap, height: bitmap.h)
66
67
  end.real
67
68
  end
68
69
 
69
- def process_info(bitmap = nil)
70
+ def process_info
70
71
  raw_polygons = @whole_tile.to_raw_polygons
71
72
 
72
73
  compress_time = Benchmark.measure do
@@ -83,14 +84,16 @@ module Contrek
83
84
  outer: (@whole_tile.benchmarks[:outer] * 1000).round(3),
84
85
  inner: (@whole_tile.benchmarks[:inner] * 1000).round(3),
85
86
  compress: ((compress_time * 1000).round(3) if @options.has_key?(:compress))
86
- }.compact
87
+ }.compact,
88
+ width: @maximum_width,
89
+ height: @height
87
90
  }
88
91
  Contrek::Finder::Result.new(raw_polygons, metadata)
89
92
  end
90
93
 
91
94
  private
92
95
 
93
- def process_tiles!(bitmap)
96
+ def process_tiles!(bitmap, height:)
94
97
  arriving_tiles = []
95
98
  loop do
96
99
  tile = @tiles.pop
@@ -110,7 +113,7 @@ module Contrek
110
113
 
111
114
  # puts "start = #{start_x} end = #{end_x}"
112
115
 
113
- cluster = Cluster.new(finder: self, height: bitmap.h, start_x:, end_x:)
116
+ cluster = Cluster.new(finder: self, height: height, start_x:, end_x:)
114
117
  if twin_tile.start_x == (tile.end_x - 1)
115
118
  cluster.add(tile)
116
119
  cluster.add(twin_tile)
@@ -0,0 +1,10 @@
1
+ module Contrek
2
+ module Concurrent
3
+ class HorizontalMerger < Merger
4
+ def add_tile(result)
5
+ translate(result, @current_x)
6
+ super
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,52 @@
1
+ module Contrek
2
+ module Concurrent
3
+ class Merger < Finder
4
+ prepend Poolable
5
+
6
+ def initialize(options: {})
7
+ @initialize_time = 0
8
+ @current_x = 0
9
+ @tiles = Queue.new
10
+ @whole_tile = nil
11
+ @options = options
12
+ end
13
+
14
+ def add_tile(result)
15
+ @height ||= result.metadata[:height]
16
+ end_x = @current_x + result.metadata[:width]
17
+
18
+ tile = Tile.new(
19
+ finder: self,
20
+ start_x: @current_x,
21
+ end_x: end_x,
22
+ name: @tiles.size.to_s
23
+ )
24
+ tile.assign_raw_polygons!(result[:polygons])
25
+
26
+ @tiles << tile
27
+ @maximum_width = end_x
28
+ @current_x = end_x - 1
29
+ end
30
+
31
+ def process_info
32
+ process_tiles!(nil, height: @height)
33
+ super
34
+ end
35
+
36
+ private
37
+
38
+ def translate(result, offset)
39
+ result.polygons.each do |polygon|
40
+ polygon[:outer].each { |p| p[:x] += offset }
41
+ polygon[:inner].each do |sequence|
42
+ sequence.each { |p| p[:x] += offset }
43
+ end
44
+ if polygon.key?(:bounds)
45
+ polygon[:bounds][:min_x] += offset
46
+ polygon[:bounds][:max_x] += offset
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -74,8 +74,6 @@ module Contrek
74
74
  "#{self.class}[#{@name}]"
75
75
  end
76
76
 
77
- private
78
-
79
77
  def assign_raw_polygons!(raw_polylines)
80
78
  @shapes = []
81
79
  raw_polylines.each do |raw_polyline|
@@ -0,0 +1,44 @@
1
+ module Contrek
2
+ module Concurrent
3
+ class VerticalMerger < Merger
4
+ def add_tile(result)
5
+ transpose(result)
6
+ if @tiles.size > 0
7
+ translate(result, @current_x)
8
+ adjust(result)
9
+ end
10
+ super
11
+ end
12
+
13
+ def process_info
14
+ transpose(super)
15
+ end
16
+
17
+ private
18
+
19
+ def adjust(result)
20
+ result.polygons.each do |polygon|
21
+ polygon[:outer].rotate!(1)
22
+ polygon[:inner].each do |sequence|
23
+ sequence.rotate!(1)
24
+ end
25
+ end
26
+ end
27
+
28
+ def transpose(result)
29
+ result.metadata_hash[:width], result.metadata_hash[:height] = result.metadata_hash[:height], result.metadata_hash[:width]
30
+ result.polygons.each do |polygon|
31
+ polygon[:outer].each { |p| p[:x], p[:y] = p[:y], p[:x] }
32
+ polygon[:inner].each do |sequence|
33
+ sequence.each { |p| p[:x], p[:y] = p[:y], p[:x] }
34
+ end
35
+ if polygon.key?(:bounds)
36
+ polygon[:bounds][:min_x], polygon[:bounds][:min_y] = polygon[:bounds][:min_y], polygon[:bounds][:min_x]
37
+ polygon[:bounds][:max_x], polygon[:bounds][:max_y] = polygon[:bounds][:max_y], polygon[:bounds][:max_x]
38
+ end
39
+ end
40
+ result
41
+ end
42
+ end
43
+ end
44
+ end
@@ -47,6 +47,8 @@ module Contrek
47
47
  groups: @node_cluster.sequences.size,
48
48
  groups_names: @node_cluster.root_nodes.map(&:name).join,
49
49
  benchmarks: format_benchmarks,
50
+ width: @source_bitmap.w,
51
+ height: @source_bitmap.h,
50
52
  treemap: (@node_cluster.treemap if @options.has_key?(:treemap))
51
53
  }
52
54
  Result.new(@node_cluster.polygons, metadata)
@@ -5,7 +5,7 @@ module Contrek
5
5
  start_p = @points[0]
6
6
  end_p = @points[1]
7
7
  dir = seq_dir(start_p, end_p)
8
- @points[2..].map.with_index do |point, i|
8
+ @points[2..].each.with_index do |point, i|
9
9
  if (act_seq = seq_dir(end_p, point)) == dir
10
10
  @points.delete_at(@points.index(end_p))
11
11
  else
@@ -1,3 +1,3 @@
1
1
  module Contrek
2
- VERSION = "1.1.4"
2
+ VERSION = "1.1.5"
3
3
  end
data/lib/contrek.rb CHANGED
@@ -3,7 +3,7 @@ require "contrek/bitmaps/painting"
3
3
  require "contrek/bitmaps/bitmap"
4
4
  require "contrek/bitmaps/chunky_bitmap"
5
5
  require "contrek/bitmaps/png_bitmap"
6
- require "contrek/bitmaps/custom_bitmap"
6
+ require "contrek/bitmaps/raw_bitmap"
7
7
  require "contrek/bitmaps/rgb_color"
8
8
  require "contrek/bitmaps/rgb_cpp_color"
9
9
  require "contrek/finder/bounds"
@@ -32,6 +32,9 @@ require "contrek/finder/concurrent/tile"
32
32
  require "contrek/finder/concurrent/polyline"
33
33
  require "contrek/finder/concurrent/cluster"
34
34
  require "contrek/finder/concurrent/finder"
35
+ require "contrek/finder/concurrent/merger"
36
+ require "contrek/finder/concurrent/horizontal_merger"
37
+ require "contrek/finder/concurrent/vertical_merger"
35
38
  require "contrek/finder/concurrent/cursor"
36
39
  require "contrek/map/mercator_projection"
37
40
  require "contrek/matchers/matcher"
@@ -43,6 +46,9 @@ require "contrek/reducers/uniq_reducer"
43
46
  require "contrek/reducers/visvalingam_reducer"
44
47
  require "cpp_polygon_finder"
45
48
  require "contrek/cpp/cpp_concurrent_finder"
49
+ require "contrek/cpp/cpp_concurrent_merger"
50
+ require "contrek/cpp/cpp_concurrent_horizontal_merger"
51
+ require "contrek/cpp/cpp_concurrent_vertical_merger"
46
52
  require "contrek/results/cpp_result"
47
53
 
48
54
  module Contrek
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
4
+ version: 1.1.5
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-02-28 00:00:00.000000000 Z
11
+ date: 2026-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -154,6 +154,8 @@ files:
154
154
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/Bitmap.h
155
155
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.cpp
156
156
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.h
157
+ - ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.cpp
158
+ - ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.h
157
159
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RemoteFastPngBitmap.cpp
158
160
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RemoteFastPngBitmap.h
159
161
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/spng.c
@@ -184,8 +186,12 @@ files:
184
186
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/FakeCluster.h
185
187
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp
186
188
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.h
189
+ - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.cpp
190
+ - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.h
187
191
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Hub.cpp
188
192
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Hub.h
193
+ - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.cpp
194
+ - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.h
189
195
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp
190
196
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h
191
197
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/PartPool.cpp
@@ -206,6 +212,8 @@ files:
206
212
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.h
207
213
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp
208
214
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.h
215
+ - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.cpp
216
+ - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.h
209
217
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/optionparser.h
210
218
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/Matcher.cpp
211
219
  - ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/Matcher.h
@@ -228,13 +236,16 @@ files:
228
236
  - lib/contrek.rb
229
237
  - lib/contrek/bitmaps/bitmap.rb
230
238
  - lib/contrek/bitmaps/chunky_bitmap.rb
231
- - lib/contrek/bitmaps/custom_bitmap.rb
232
239
  - lib/contrek/bitmaps/painting.rb
233
240
  - lib/contrek/bitmaps/png_bitmap.rb
241
+ - lib/contrek/bitmaps/raw_bitmap.rb
234
242
  - lib/contrek/bitmaps/rgb_color.rb
235
243
  - lib/contrek/bitmaps/rgb_cpp_color.rb
236
244
  - lib/contrek/bitmaps/sample_generator.rb
237
245
  - lib/contrek/cpp/cpp_concurrent_finder.rb
246
+ - lib/contrek/cpp/cpp_concurrent_horizontal_merger.rb
247
+ - lib/contrek/cpp/cpp_concurrent_merger.rb
248
+ - lib/contrek/cpp/cpp_concurrent_vertical_merger.rb
238
249
  - lib/contrek/finder/bounds.rb
239
250
  - lib/contrek/finder/concurrent/clipped_polygon_finder.rb
240
251
  - lib/contrek/finder/concurrent/cluster.rb
@@ -242,8 +253,10 @@ files:
242
253
  - lib/contrek/finder/concurrent/end_point.rb
243
254
  - lib/contrek/finder/concurrent/fake_cluster.rb
244
255
  - lib/contrek/finder/concurrent/finder.rb
256
+ - lib/contrek/finder/concurrent/horizontal_merger.rb
245
257
  - lib/contrek/finder/concurrent/hub.rb
246
258
  - lib/contrek/finder/concurrent/listable.rb
259
+ - lib/contrek/finder/concurrent/merger.rb
247
260
  - lib/contrek/finder/concurrent/part.rb
248
261
  - lib/contrek/finder/concurrent/partitionable.rb
249
262
  - lib/contrek/finder/concurrent/polyline.rb
@@ -253,6 +266,7 @@ files:
253
266
  - lib/contrek/finder/concurrent/sequence.rb
254
267
  - lib/contrek/finder/concurrent/shape.rb
255
268
  - lib/contrek/finder/concurrent/tile.rb
269
+ - lib/contrek/finder/concurrent/vertical_merger.rb
256
270
  - lib/contrek/finder/list.rb
257
271
  - lib/contrek/finder/list_entry.rb
258
272
  - lib/contrek/finder/listable.rb