contrek 1.2.3 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07f6097c256b6d44373aa941b969b12dd68630e168c72b29d1ae478bb5f48186
4
- data.tar.gz: 7ec31b1ef4413e679e31a123f367d0a86ed332160455c8024ea185ca67c27020
3
+ metadata.gz: 32d1d662c0c6c0c1dc6ffcde36090b188e7d3abd39fc5dd7ba0887aa62526b27
4
+ data.tar.gz: df8b75836abf466043587f22995f12e13c3ed908b60a4fcda97216e803458540
5
5
  SHA512:
6
- metadata.gz: c03213b7499e5741bf3c90044251a5ba6fc606905f3c72ff93462a62ffc116ebae7ce211fb2a201862d874ae865df048e45cf85beed094bc60967d2b4dd6fc34
7
- data.tar.gz: ae891e55d9d9ef5503f654d79c7187713579c6fa1c699736d446d6b19497ddb9d30723a26e98a77261481f364466b7d891962e9937433a65e6e341f240129faa
6
+ metadata.gz: 33c9e4906109bcd7cee694401b3946b0fe2e5800c551bc20a4ea428bdb7cbe7eb456a2e7cadd57327ca93bf65491284e4566b2caaa9e0ea28a5c771c8e50510b
7
+ data.tar.gz: ede6984f06ebef2b9672e1f1c0569428b25332e9bb14249a8341fdc1e4f11c493a066015a496bd9d19baec57a97bf98d4d99d23262a4a7dac93757c129e8c441
data/CHANGELOG.md CHANGED
@@ -95,7 +95,14 @@ 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.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contrek (1.2.3)
4
+ contrek (1.2.4)
5
5
  chunky_png (~> 1.4)
6
6
  concurrent-ruby (~> 1.3.5)
7
7
  rice (= 4.5.0)
@@ -17,6 +17,8 @@
17
17
  #include <cstring>
18
18
  #include <algorithm>
19
19
  #include <cstdio>
20
+ #include <sys/resource.h>
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
@@ -303,7 +315,7 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height) {
303
315
  }
304
316
 
305
317
  size_t row_size = static_cast<size_t>(total_width) * 4;
306
- // main strpes loop
318
+ // main stripes loop
307
319
  for (uint32_t current_y_offset = 0; current_y_offset < total_height; current_y_offset += stripe_height) {
308
320
  int uncovered_height = total_height - current_y_offset;
309
321
 
@@ -366,4 +378,5 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height) {
366
378
 
367
379
  void Tests::test_i() {
368
380
  stream_png_image("../images/graphs_1024x1024.png", 300);
381
+ std::cout << "Memory usage peak: " << get_peak_rss() << " MB" << std::endl;
369
382
  }
@@ -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);
@@ -86,9 +86,8 @@ void Cursor::traverse_outer(Part* act_part,
86
86
  auto& q_set = new_position->end_point()->queues();
87
87
  auto it = std::find_if(q_set.begin(), q_set.end(), [&](Queueable<Point>* q) {
88
88
  Part* p = static_cast<Part*>(q);
89
- return p->versus() == -versus && p->polyline()->tile != act_part->polyline()->tile;
89
+ return (p->mirror || act_part->mirror || p->versus() == -versus) && p->polyline()->tile != act_part->polyline()->tile;
90
90
  });
91
-
92
91
  Part* part = nullptr;
93
92
  if (it != q_set.end()) {
94
93
  part = static_cast<Part*>(*it);
@@ -97,24 +96,26 @@ void Cursor::traverse_outer(Part* act_part,
97
96
  const auto n = all_parts.size();
98
97
  Part *last_last_part = n >= 2 ? all_parts[n - 2] : nullptr;
99
98
  if (last_last_part != part) {
99
+ bool all_seam = false;
100
100
  if (n >= 2) {
101
- bool all_seam = true;
101
+ all_seam = true;
102
102
  for (std::size_t i = all_parts.size() - 2; i < all_parts.size(); ++i) {
103
103
  if (all_parts[i]->type != Part::SEAM) {
104
104
  all_seam = false;
105
105
  break;
106
106
  }
107
107
  }
108
- if (all_seam) break;
109
108
  }
110
- if (shapes_sequence_lookup.insert(part->polyline()->shape).second) {
111
- shapes_sequence.push_back(part->polyline()->shape);
109
+ if (!all_seam) {
110
+ if (shapes_sequence_lookup.insert(part->polyline()->shape).second) {
111
+ shapes_sequence.push_back(part->polyline()->shape);
112
+ }
113
+ part->next_position(new_position);
114
+ part->dead_end = true;
115
+ act_part = part;
116
+ jumped_to_new_part = true;
117
+ break;
112
118
  }
113
- part->next_position(new_position);
114
- part->dead_end = true;
115
- act_part = part;
116
- jumped_to_new_part = true;
117
- break;
118
119
  }
119
120
  }
120
121
  if (!jumped_to_new_part) {
@@ -65,7 +65,13 @@ void Part::orient()
65
65
  { if (this->size <= 1 || (this->size == 2 && this->inverts)) {
66
66
  this->versus_ = 0;
67
67
  } else {
68
- this->versus_ = (this->tail->payload->y - this->head->payload->y) > 0 ? 1 : -1;
68
+ int diff = this->tail->payload->y - this->head->payload->y;
69
+ if (diff == 0) {
70
+ this->mirror = true;
71
+ this->versus_ = 0;
72
+ } else {
73
+ this->versus_ = diff > 0 ? 1 : -1;
74
+ }
69
75
  }
70
76
  }
71
77
 
@@ -78,6 +84,7 @@ std::string Part::inspect() {
78
84
  std::stringstream ss;
79
85
  ss << "part " << part_index
80
86
  << " (versus=" << this->versus_
87
+ << " mirror=" << this->mirror
81
88
  << " inv=" << this->inverts
82
89
  << " trm=" << this->trasmuted
83
90
  << " touched=" << this->touched_
@@ -33,6 +33,7 @@ class Part : public Queueable<Point> {
33
33
  bool inverts = false;
34
34
  bool trasmuted = false;
35
35
  bool dead_end = false;
36
+ bool mirror = false;
36
37
  Part* next = nullptr;
37
38
  Part* prev = nullptr;
38
39
  Part* circular_next = nullptr;
@@ -87,10 +87,16 @@ void Partitionable::trasmute_parts()
87
87
  }
88
88
  return false;
89
89
  });
90
- if (count == inside->size && count < inside_compare->size) {
91
- inside->type = Part::EXCLUSIVE;
92
- inside->trasmuted = true;
93
- break;
90
+ if (count == inside->size) {
91
+ if (count < inside_compare->size) {
92
+ inside->type = Part::EXCLUSIVE;
93
+ inside->trasmuted = true;
94
+ break;
95
+ } else if ( count == inside_compare->size &&
96
+ inside->next == nullptr &&
97
+ inside_compare->prev == nullptr) {
98
+ inside->mirror = true;
99
+ }
94
100
  }
95
101
  }
96
102
  }
@@ -115,7 +115,7 @@ module Contrek
115
115
  new_position.end_point.tracked_outer = true
116
116
  versus = act_part.versus
117
117
  part = new_position.end_point.queues.find do |p|
118
- p.versus == -versus && p.polyline.tile != act_part.polyline.tile
118
+ (p.mirror || act_part.mirror || p.versus == -versus) && p.polyline.tile != act_part.polyline.tile
119
119
  end
120
120
  if part
121
121
  if all_parts[-2] != part
@@ -8,7 +8,7 @@ module Contrek
8
8
  ADDED = 2
9
9
 
10
10
  attr_reader :polyline, :touched
11
- attr_accessor :next, :circular_next, :prev, :type, :dead_end, :inverts, :trasmuted, :versus
11
+ attr_accessor :next, :circular_next, :prev, :type, :dead_end, :inverts, :trasmuted, :versus, :mirror
12
12
  def initialize(type, polyline)
13
13
  @type = type
14
14
  @polyline = polyline
@@ -20,6 +20,7 @@ module Contrek
20
20
  @inverts = false
21
21
  @trasmuted = false
22
22
  @versus = 0
23
+ @mirror = false
23
24
  end
24
25
 
25
26
  def is?(type)
@@ -56,7 +57,7 @@ module Contrek
56
57
  end
57
58
 
58
59
  def inspect
59
- "part #{polyline.parts.index(self)} (versus=#{@versus} inv=#{@inverts} trm=#{@trasmuted} touched=#{@touched} dead_end =#{@dead_end}, #{size}x) of #{polyline.info} (#{name}) (#{to_a.map { |e| "[#{e[:x]},#{e[:y]}]" }.join})"
60
+ "part #{polyline.parts.index(self)} (mir=#{@mirror} versus=#{@versus} inv=#{@inverts} trm=#{@trasmuted} touched=#{@touched} dead_end =#{@dead_end}, #{size}x) of #{polyline.named} (#{name}) (#{to_a.map { |e| "[#{e[:x]},#{e[:y]}]" }.join})"
60
61
  end
61
62
 
62
63
  def innerable?
@@ -67,7 +68,13 @@ module Contrek
67
68
  @versus = if size <= 1 || (size == 2 && @inverts)
68
69
  0
69
70
  else
70
- (tail.payload[:y] - head.payload[:y]).positive? ? 1 : -1
71
+ diff = tail.payload[:y] - head.payload[:y]
72
+ if diff == 0
73
+ @mirror = true
74
+ 0
75
+ else
76
+ diff.positive? ? 1 : -1
77
+ end
71
78
  end
72
79
  end
73
80
 
@@ -73,6 +73,10 @@ module Contrek
73
73
  inside.trasmuted = true
74
74
  break
75
75
  end
76
+ if count == inside.size && count == inside_compare.size &&
77
+ inside.next.nil? && inside_compare.prev.nil?
78
+ inside.mirror = true
79
+ end
76
80
  end
77
81
  end
78
82
  end
@@ -1,3 +1,3 @@
1
1
  module Contrek
2
- VERSION = "1.2.3"
2
+ VERSION = "1.2.5"
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.3
4
+ version: 1.2.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-05-23 00:00:00.000000000 Z
11
+ date: 2026-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec