contrek 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0df584b1fe92f70cb27f479a6d7705545f662b8758a4843bf966fe92d90c4c9d
4
- data.tar.gz: 2dfe6f8b0cc96298858f5055158ba439644d24a9921166c572e91cd5df17780b
3
+ metadata.gz: ca0c4f7e37692b2d2e5b17556ed67bee4b624511ddf1dc3865ecf910e8f46aeb
4
+ data.tar.gz: 66b9af71020dc6a86da1abace1d0467807b9212e207b71a7f6337b2257a34192
5
5
  SHA512:
6
- metadata.gz: d97742b1bc4f3688a8d083d25873da8db709b69c42c8068d02cfe6fe3569acac159f3d1a37c31128a7809b0e9851ba5e9da15c801f18c514189e53f4bc39440a
7
- data.tar.gz: ee34393dc9713f62de0560b5d6538e7e9ffd6ff38358429f14f7ab7efb3cde201f2461d4ec209ed906a0a0e1e6aa0eb013040dddf290856400f632131e4c3622
6
+ metadata.gz: 63ba135c86c7849fda12e542af23a2746681f8aade888daa9269438c1601e8184215b3fe5081ee9caabce923e7dfafd8bcc3613fb06cf2ec9997193ff831e5c7
7
+ data.tar.gz: dd691e542a8722be9532d3c0f206d4b2a2499874f0be52786c99822036d3e0e463ffb6d301bceae44e8f487f434eeff98c9a931cffe27eab3ed5d249daf060e5
data/CHANGELOG.md CHANGED
@@ -95,7 +95,20 @@ All notable changes to this project will be documented in this file.
95
95
 
96
96
  ## [1.2.3] - 2026-05-23
97
97
  ### Changed
98
- ### Changed
99
98
  * **SVG Conversion:** Added utility methods to convert point coordinates directly into SVG paths.
100
99
  * **Contrek API & RAII Architecture:** Refactored the Contrek API to utilize an RAII (Resource Acquisition Is Initialization) pattern, safely wrapping both the trace engine and the processing results within a unified context lifecycle shell.
101
100
  * **ProcessResult Memory Management:** Updated `ProcessResult` to properly manage resource deallocation during cloning operations, ensuring deep-copied or moved internal points are automatically and safely freed when the context scope ends.
101
+
102
+ ## [1.2.4] - 2026-05-26
103
+ ### Changed
104
+ * Fixed an issue in connectivity8 tracing mode that, under specific rare conditions, disrupted the topological continuity of external contours in favor of internal ones.
105
+
106
+ ## [1.2.5] - 2026-05-27
107
+ ### Changed
108
+ - **Refactored `ProcessResult.clone()`:** Switched from fragmented dynamic allocation to a contiguous `std::vector` with explicit `.reserve()`. Eliminates heap fragmentation during high-res streaming.
109
+
110
+ ## [1.2.6] - 2026-05-31
111
+ ### Changed
112
+ - **Refactored `spng.c` function `rgb8_row_to_rgba8`:** Extended a loop counter to `size_t` (previously limited to `uint32_t`), which was causing segmentation faults when reading massive images (e.g., 81920x81920).
113
+ - **Refactored `RawBitmap.define` function:** Updated area and size calculations to use full 64-bit integers.
114
+ - **Refactored `PolygonFinder.to_svg_stream()` function:** Optimized performance to efficiently handle massive SVG streams of 2 GB and beyond.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contrek (1.2.4)
4
+ contrek (1.2.6)
5
5
  chunky_png (~> 1.4)
6
6
  concurrent-ruby (~> 1.3.5)
7
7
  rice (= 4.5.0)
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  #include "Tests.h"
11
+ #include <sys/resource.h>
11
12
  #include <string.h>
12
13
  #include <iostream>
13
14
  #include <list>
@@ -17,6 +18,7 @@
17
18
  #include <cstring>
18
19
  #include <algorithm>
19
20
  #include <cstdio>
21
+
20
22
  #include "polygon/finder/PolygonFinder.h"
21
23
  #include "polygon/finder/concurrent/ClippedPolygonFinder.h"
22
24
  #include "polygon/bitmaps/Bitmap.h"
@@ -260,6 +262,16 @@ void Tests::test_h()
260
262
  delete right_result;
261
263
  }
262
264
 
265
+ double get_peak_rss() {
266
+ struct rusage r_usage;
267
+ getrusage(RUSAGE_SELF, &r_usage);
268
+ #ifdef __APPLE__
269
+ return r_usage.ru_maxrss / (1024.0 * 1024.0);
270
+ #else
271
+ return r_usage.ru_maxrss / 1024.0;
272
+ #endif
273
+ }
274
+
263
275
  /* In this example, PNG data is read by streaming into a user-defined buffer height.
264
276
  Contrek scans each stripe and extracts the polygons. Finally, it merges all
265
277
  polygons from every stripe as if they had been read from a single image and saves
@@ -267,7 +279,7 @@ void Tests::test_h()
267
279
  This approach allows for processing large PNG files on systems where memory
268
280
  would otherwise be insufficient.
269
281
  */
270
- void stream_png_image(const std::string& filepath, uint32_t stripe_height) {
282
+ void stream_png_image(const std::string& filepath, uint32_t stripe_height, bool generate_svg = false, bool generate_png = false) {
271
283
  std::vector<ProcessResult*> result_clones;
272
284
  std::vector<std::string> varguments = {};
273
285
  VerticalMerger vmerger(0, &varguments);
@@ -293,6 +305,7 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height) {
293
305
 
294
306
  // allocates stripe buffer
295
307
  RawBitmap stripe_bitmap;
308
+ std::cout << total_width << " " << stripe_height << std::endl;
296
309
  stripe_bitmap.define(total_width, stripe_height, 4, true);
297
310
  RGBNotMatcher not_matcher(-1);
298
311
 
@@ -303,7 +316,8 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height) {
303
316
  }
304
317
 
305
318
  size_t row_size = static_cast<size_t>(total_width) * 4;
306
- // main strpes loop
319
+ int stripe_count = 0;
320
+ // main stripes loop
307
321
  for (uint32_t current_y_offset = 0; current_y_offset < total_height; current_y_offset += stripe_height) {
308
322
  int uncovered_height = total_height - current_y_offset;
309
323
 
@@ -332,12 +346,13 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height) {
332
346
  PolygonFinder polygon_finder(&stripe_bitmap, &not_matcher, nullptr, &finder_arguments);
333
347
  ProcessResult *result = polygon_finder.process_info();
334
348
  if (result) {
335
- std::cout << "Founds polygons: " << result->groups << std::endl;
349
+ std::cout << "stripe " << stripe_count << ": found polygons " << result->groups << std::endl;
336
350
  ProcessResult* safe_result = result->clone();
337
351
  result_clones.push_back(safe_result);
338
352
  vmerger.add_tile(*safe_result);
339
353
  delete result;
340
354
  }
355
+ stripe_count++;
341
356
  }
342
357
 
343
358
  std::cout << "Merging polygons ..." << std::endl;
@@ -345,15 +360,20 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height) {
345
360
 
346
361
  if (merged_result) {
347
362
  std::cout << "Founds total polygons: " << merged_result->groups << std::endl;
348
- /*RawBitmap full_bitmap;
349
- full_bitmap.define(total_width, total_height, 4, true);
350
- full_bitmap.fill(255, 255, 255);
351
- merged_result->draw_on_bitmap(full_bitmap);
352
- std::cout << "Saving whole png ..." << std::endl;
353
- if (full_bitmap.save_to_png("whole.png")) {
354
- std::cout << "Png saved!" << std::endl;
355
- }*/
356
- merged_result->save_svg("whole.svg");
363
+ if (generate_png) {
364
+ RawBitmap full_bitmap;
365
+ full_bitmap.define(total_width, total_height, 4, true);
366
+ full_bitmap.fill(255, 255, 255);
367
+ merged_result->draw_on_bitmap(full_bitmap);
368
+ std::cout << "Saving whole png ..." << std::endl;
369
+ if (full_bitmap.save_to_png("whole.png")) {
370
+ std::cout << "Png saved!" << std::endl;
371
+ }
372
+ }
373
+ if (generate_svg) {
374
+ merged_result->save_svg("whole.svg");
375
+ std::cout << "Svg saved!" << std::endl;
376
+ }
357
377
  }
358
378
  delete merged_result;
359
379
  // frees memory
@@ -366,4 +386,5 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height) {
366
386
 
367
387
  void Tests::test_i() {
368
388
  stream_png_image("../images/graphs_1024x1024.png", 300);
389
+ std::cout << "Memory usage peak: " << get_peak_rss() << " MB" << std::endl;
369
390
  }
@@ -56,16 +56,16 @@ void RawBitmap::draw_pixel(int x, int y, unsigned char r, unsigned char g, unsig
56
56
  if (bpp > 3) p[3] = a;
57
57
  }
58
58
 
59
- uint32_t RawBitmap::define(uint width, uint height, int bytes_per_pixel, bool clear)
60
- { this->width = width;
59
+ size_t RawBitmap::define(uint width, uint height, int bytes_per_pixel, bool clear)
60
+ { this->width = width;
61
61
  this->height = height;
62
62
  this->bpp = bytes_per_pixel;
63
- uint32_t dimension = (static_cast<uint32_t>(width) * static_cast<uint32_t>(height)) * bytes_per_pixel;
63
+ size_t dimension = static_cast<size_t>(width) * static_cast<size_t>(height) * static_cast<size_t>(bytes_per_pixel);
64
64
  this->image = std::make_unique<unsigned char[]>(dimension);
65
65
  if (clear) {
66
66
  std::fill(image.get(), image.get() + dimension, 0);
67
67
  }
68
- return dimension;
68
+ return static_cast<uint32_t>(dimension);
69
69
  }
70
70
 
71
71
  void RawBitmap::fill(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
@@ -25,7 +25,7 @@ class RawBitmap : public Bitmap {
25
25
  unsigned int rgb_value_at(int x, int y);
26
26
  const unsigned char* get_row_ptr(int y) const;
27
27
  int get_bytes_per_pixel() const;
28
- uint32_t define(uint width, uint height, int bytes_per_pixel, bool clear = false);
28
+ size_t define(uint width, uint height, int bytes_per_pixel, bool clear = false);
29
29
  void draw_pixel(int x, int y, unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255);
30
30
  bool save_to_png(const std::string& filename);
31
31
  void fill(unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255);
@@ -534,7 +534,7 @@ static void u16_row_to_bigendian(void *row, size_t size)
534
534
  write_u16(&px[i], px[i]);
535
535
  }
536
536
  }
537
-
537
+ /*
538
538
  static void rgb8_row_to_rgba8(const unsigned char *row, unsigned char *out, uint32_t n)
539
539
  {
540
540
  uint32_t i;
@@ -543,6 +543,22 @@ static void rgb8_row_to_rgba8(const unsigned char *row, unsigned char *out, uint
543
543
  memcpy(out + i * 4, row + i * 3, 3);
544
544
  out[i*4+3] = 255;
545
545
  }
546
+ }*/
547
+
548
+ /* Fix overflow Gigapixel images */
549
+ static void rgb8_row_to_rgba8(const unsigned char *row, unsigned char *out, uint32_t n)
550
+ {
551
+ size_t i;
552
+ for(i=0; i < n; i++)
553
+ {
554
+ out[0] = row[0];
555
+ out[1] = row[1];
556
+ out[2] = row[2];
557
+ out[3] = 255;
558
+
559
+ out += 4;
560
+ row += 3;
561
+ }
546
562
  }
547
563
 
548
564
  static unsigned num_channels(const struct spng_ihdr *ihdr)
@@ -62,6 +62,7 @@ struct Point {
62
62
  return x == other.x && y == other.y;
63
63
  }
64
64
  Point(int x_, int y_) : x(x_), y(y_) {}
65
+ Point() : x(0), y(0) {}
65
66
  };
66
67
 
67
68
  class Node : public Listable {
@@ -103,7 +103,6 @@ void PolygonFinder::scan() {
103
103
  ProcessResult* PolygonFinder::process_info() {
104
104
  ProcessResult *pr = new ProcessResult();
105
105
  pr->groups = this->node_cluster->sequences.size();
106
- pr->polygons = std::move(this->node_cluster->polygons);
107
106
  pr->benchmarks = std::move(this->reports);
108
107
  pr->treemap = this->node_cluster->treemap;
109
108
  pr->width = this->source_bitmap->w();
@@ -124,6 +123,7 @@ ProcessResult* PolygonFinder::process_info() {
124
123
  pr->named_sequence = sequence;
125
124
  }
126
125
  else pr->named_sequence = "";
126
+ pr->polygons = std::move(this->node_cluster->polygons);
127
127
  return(pr);
128
128
  }
129
129
 
@@ -63,7 +63,7 @@ struct ProcessResult {
63
63
  std::list<Polygon> polygons;
64
64
  std::string named_sequence;
65
65
  std::vector<std::pair<int, int>> treemap;
66
- std::vector<std::unique_ptr<Point>> cloned_points_storage;
66
+ std::vector<Point> cloned_points_storage;
67
67
 
68
68
  void draw_on_bitmap(RawBitmap& canvas) const;
69
69
 
@@ -119,22 +119,25 @@ struct ProcessResult {
119
119
 
120
120
  for (const auto& poly : this->polygons) {
121
121
  Polygon new_poly;
122
- // bounds
123
122
  new_poly.bounds = poly.bounds;
123
+ new_poly.outer.reserve(poly.outer.size());
124
124
  // outer
125
125
  for (const Point* p : poly.outer) {
126
126
  if (p) {
127
- new_res->cloned_points_storage.push_back(std::make_unique<Point>(p->x, p->y));
128
- new_poly.outer.push_back(new_res->cloned_points_storage.back().get());
127
+ new_res->cloned_points_storage.push_back(Point(p->x, p->y));
128
+ size_t idx = new_res->cloned_points_storage.size() - 1;
129
+ new_poly.outer.push_back(&new_res->cloned_points_storage[idx]);
129
130
  }
130
131
  }
131
132
  // inner
132
133
  for (const auto& seq : poly.inner) {
133
134
  std::vector<Point*> new_seq;
135
+ new_seq.reserve(seq.size());
134
136
  for (const Point* p : seq) {
135
137
  if (p) {
136
- new_res->cloned_points_storage.push_back(std::make_unique<Point>(p->x, p->y));
137
- new_seq.push_back(new_res->cloned_points_storage.back().get());
138
+ new_res->cloned_points_storage.push_back(Point(p->x, p->y));
139
+ size_t idx = new_res->cloned_points_storage.size() - 1;
140
+ new_seq.push_back(&new_res->cloned_points_storage[idx]);
138
141
  }
139
142
  }
140
143
  new_poly.inner.push_back(new_seq);
@@ -144,50 +147,37 @@ struct ProcessResult {
144
147
  return new_res;
145
148
  }
146
149
 
147
- std::string to_svg() const {
148
- std::vector<std::string> lines;
149
- lines.push_back(
150
- "<svg xmlns=\"http://www.w3.org/2000/svg\" "
151
- "width=\"" + std::to_string(width) +
152
- "\" height=\"" + std::to_string(height) + "\">");
150
+ void to_svg_stream(std::ostream& os) const {
151
+ os << "<svg xmlns=\"http://www.w3.org/2000/svg\" "
152
+ << "width=\"" << width << "\" height=\"" << height << "\">\n";
153
153
  for (const auto& poly : polygons) {
154
- { // outer
155
- std::ostringstream pts;
154
+ // --- OUTER (Red) ---
155
+ if (!poly.outer.empty()) {
156
+ os << "<polygon points=\"";
156
157
  bool first = true;
157
158
  for (const Point* p : poly.outer) {
158
159
  if (!p) continue;
159
- if (!first)
160
- pts << " ";
160
+ if (!first) os << " ";
161
161
  first = false;
162
- pts << p->x << "," << p->y;
162
+ os << p->x << "," << p->y;
163
163
  }
164
- lines.push_back(
165
- "<polygon points=\"" + pts.str() +
166
- "\" fill=\"none\" stroke=\"red\" stroke-width=\"1\"/>");
164
+ os << "\" fill=\"none\" stroke=\"red\" stroke-width=\"1\"/>\n";
167
165
  }
168
- // inner
166
+ // --- INNER (Green) ---
169
167
  for (const auto& sequence : poly.inner) {
170
168
  if (sequence.empty()) continue;
171
- std::ostringstream pts;
169
+ os << "<polygon points=\"";
172
170
  bool first = true;
173
171
  for (const Point* p : sequence) {
174
172
  if (!p) continue;
175
- if (!first) pts << " ";
173
+ if (!first) os << " ";
176
174
  first = false;
177
- pts << p->x << "," << p->y;
175
+ os << p->x << "," << p->y;
178
176
  }
179
- lines.push_back(
180
- "<polygon points=\"" + pts.str() +
181
- "\" fill=\"none\" stroke=\"green\" stroke-width=\"1\"/>");
177
+ os << "\" fill=\"none\" stroke=\"green\" stroke-width=\"1\"/>\n";
182
178
  }
183
179
  }
184
- lines.push_back("</svg>");
185
- std::ostringstream result;
186
- for (size_t i = 0; i < lines.size(); ++i) {
187
- result << lines[i];
188
- if (i + 1 < lines.size()) result << "\n";
189
- }
190
- return result.str();
180
+ os << "</svg>";
191
181
  }
192
182
 
193
183
  void save_svg(const std::string& filename) const {
@@ -195,7 +185,7 @@ struct ProcessResult {
195
185
  if (!file.is_open()) {
196
186
  throw std::runtime_error("Unable to open SVG file: " + filename);
197
187
  }
198
- file << to_svg();
188
+ to_svg_stream(file);
199
189
  file.close();
200
190
  }
201
191
  };
@@ -1,3 +1,3 @@
1
1
  module Contrek
2
- VERSION = "1.2.4"
2
+ VERSION = "1.2.6"
3
3
  end
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.2.4
4
+ version: 1.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emanuele Cesaroni
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-26 00:00:00.000000000 Z
11
+ date: 2026-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec