contrek 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +1 -7
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +0 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +30 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +3 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +41 -41
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.h +4 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.cpp +1 -0
- data/lib/contrek/finder/concurrent/cursor.rb +0 -3
- data/lib/contrek/finder/concurrent/part.rb +25 -1
- data/lib/contrek/finder/concurrent/partitionable.rb +39 -34
- data/lib/contrek/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0fff9996602ac84d2a5d96e840150e59cb457bc8050c53758efac88f170bef79
|
|
4
|
+
data.tar.gz: 40958fef1d22e2458beae7291cfdca85102eda4983bf01ef468c334ddce99ec8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7f8f1f1368c472fb9770a892874f54817a67745929a0d772c5a9cc3e387fcd868e5662ad67e341d6a53232b6be4eb028438545535adfb05be3d69c497c3918da
|
|
7
|
+
data.tar.gz: cc7991d57ce63d43c98165c1f3684d1f31d05993a6e4a20e40437d70d5b251a1de0032ca354f44d2bb8efbf30286423f6b2925e01da0f039c71661427f4d2716
|
data/CHANGELOG.md
CHANGED
|
@@ -122,3 +122,10 @@ All notable changes to this project will be documented in this file.
|
|
|
122
122
|
|
|
123
123
|
## [1.2.9] - 2026-06-13
|
|
124
124
|
- **Streaming merger:** The streaming merger class extends VerticalMerger and adds a useful feature: the progressive extraction of contours into a disk buffer (SVG file). In this way, all extracted polygons that are no longer within the junction zone of the next stripe are removed from the system and streamed directly to disk. This incredibly reduces memory consumption, allowing the processing of very large files on machines with low memory availability, at the expense of increased processing times. An example of this technique is available in both C++ and Ruby in the repository.
|
|
125
|
+
|
|
126
|
+
## [1.3.0] - 2026-06-17
|
|
127
|
+
- **Streaming merger:** Improvements and bug fixing.
|
|
128
|
+
- **CPP code:** All structures now own 'Point' instances by value instead of raw pointers. Removed now-redundant `clone()` method; results from `process_info()` are already self-contained since points are owned by value, so the defensive deep copy is no longer needed.
|
|
129
|
+
|
|
130
|
+
## [1.3.1] - 2026-06-20
|
|
131
|
+
- **Streaming merger:** The progressive streaming extraction mode has now reached new heights of efficiency on the C++ side. This mode allows the data source to be processed in contiguous blocks. All isolated polygons, as well as those extending into the bottom strips, are removed from the list and streamed directly to the SVG file. This drastically reduces RAM requirements. An extreme test on an 81920×81920 pixel image containing a massive number of polygons (20,000,000) was processed using roughly 40 strips of 2000 pixels each in less than 300 seconds, peaking at a RAM usage of just 13GB.
|
data/Gemfile.lock
CHANGED
|
@@ -461,20 +461,14 @@ void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_h
|
|
|
461
461
|
ProcessResult *result = polygon_finder.process_info();
|
|
462
462
|
if (result) {
|
|
463
463
|
std::cout << "stripe " << stripe_count << ": found polygons " << result->groups << std::endl;
|
|
464
|
-
result_clones.push_back(result);
|
|
465
464
|
vmerger.add_tile(*result, !(current_y_offset + stripe_height < total_height));
|
|
465
|
+
delete result;
|
|
466
466
|
}
|
|
467
467
|
stripe_count++;
|
|
468
468
|
}
|
|
469
|
-
|
|
470
469
|
ProcessResult *merged_result = vmerger.process_info();
|
|
471
470
|
std::cout << "total found polygons " << merged_result->groups << std::endl;
|
|
472
471
|
delete merged_result;
|
|
473
|
-
|
|
474
|
-
// frees memory
|
|
475
|
-
for (auto c : result_clones) {
|
|
476
|
-
delete c;
|
|
477
|
-
}
|
|
478
472
|
} catch (const std::exception& e) {
|
|
479
473
|
std::cerr << "\n[ERROR] Processing exception: " << e.what() << std::endl;
|
|
480
474
|
if (shared_stream.is_open()) shared_stream.close();
|
|
@@ -61,11 +61,6 @@ void Cursor::traverse_outer(Part* act_part,
|
|
|
61
61
|
if (act_part->size == 0) return;
|
|
62
62
|
|
|
63
63
|
while (Position *position = act_part->next_position(nullptr)) {
|
|
64
|
-
if (outer_joined_polyline->size > 1 &&
|
|
65
|
-
outer_joined_polyline->head->payload == position->payload &&
|
|
66
|
-
act_part == all_parts.front()) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
64
|
outer_joined_polyline->add(position);
|
|
70
65
|
}
|
|
71
66
|
} else {
|
|
@@ -75,6 +75,36 @@ void Part::orient()
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
void Part::try_transmutation() {
|
|
79
|
+
auto& head_queues = static_cast<Position*>(this->head)->end_point()->queues();
|
|
80
|
+
if (head_queues.size() == 1) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
auto other_head_it = std::find_if(head_queues.begin(), head_queues.end(), [this](auto* queueable_ptr) {
|
|
85
|
+
Part* part = static_cast<Part*>(queueable_ptr);
|
|
86
|
+
return part != this && part->polyline()->tile == this->polyline()->tile;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (other_head_it != head_queues.end()) {
|
|
90
|
+
Part* other_head_part = static_cast<Part*>(*other_head_it);
|
|
91
|
+
auto& tail_queues = static_cast<Position*>(this->tail)->end_point()->queues();
|
|
92
|
+
auto found_in_tail = std::find(tail_queues.begin(), tail_queues.end(), other_head_part);
|
|
93
|
+
if (found_in_tail != tail_queues.end()) {
|
|
94
|
+
if ( (other_head_part->tail->payload.y == tail->payload.y && other_head_part->head->payload.y == head->payload.y) ||
|
|
95
|
+
(other_head_part->tail->payload.y == head->payload.y && other_head_part->head->payload.y == tail->payload.y)) {
|
|
96
|
+
if (this->next == nullptr && other_head_part->prev == nullptr) {
|
|
97
|
+
this->mirror = true;
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
this->type = Part::EXCLUSIVE;
|
|
101
|
+
this->trasmuted = true;
|
|
102
|
+
other_head_part->transmutation_skip = true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
78
108
|
bool Part::within(Part* other) {
|
|
79
109
|
const auto [self_min, self_max] = std::minmax(this->head->payload.y, this->tail->payload.y);
|
|
80
110
|
const auto [other_min, other_max] = std::minmax(other->head->payload.y, other->tail->payload.y);
|
|
@@ -32,9 +32,11 @@ class Part : public Queueable<Point> {
|
|
|
32
32
|
bool is(Types type);
|
|
33
33
|
bool inverts = false;
|
|
34
34
|
bool trasmuted = false;
|
|
35
|
+
bool transmutation_skip = false;
|
|
35
36
|
bool dead_end = false;
|
|
36
37
|
bool mirror = false;
|
|
37
38
|
Part* next = nullptr;
|
|
39
|
+
Part* next_seam = nullptr;
|
|
38
40
|
Part* prev = nullptr;
|
|
39
41
|
Part* circular_next = nullptr;
|
|
40
42
|
std::string toString() const { return "Part type = " + std::to_string(static_cast<uint32_t>(type)); }
|
|
@@ -51,6 +53,7 @@ class Part : public Queueable<Point> {
|
|
|
51
53
|
std::vector<EndPoint*> continuum_to(const Part& other_part) const;
|
|
52
54
|
bool within(Part* other);
|
|
53
55
|
bool same_length(Part* other);
|
|
56
|
+
void try_transmutation();
|
|
54
57
|
|
|
55
58
|
private:
|
|
56
59
|
int versus_ = 0;
|
|
@@ -32,11 +32,23 @@ void Partitionable::add_part(Part* new_part)
|
|
|
32
32
|
new_part->prev = last;
|
|
33
33
|
new_part->circular_next = this->parts_.front();
|
|
34
34
|
|
|
35
|
-
if (new_part->is(Part::SEAM))
|
|
35
|
+
if (new_part->is(Part::SEAM)) {
|
|
36
|
+
if (!this->first_seam) {
|
|
37
|
+
this->first_seam = new_part;
|
|
38
|
+
}
|
|
39
|
+
if (this->last_seam) {
|
|
40
|
+
this->last_seam->next_seam = new_part;
|
|
41
|
+
}
|
|
42
|
+
this->last_seam = new_part;
|
|
43
|
+
new_part->orient();
|
|
44
|
+
}
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
void Partitionable::partition()
|
|
39
48
|
{ this->parts_.clear();
|
|
49
|
+
this->first_seam = nullptr;
|
|
50
|
+
this->last_seam = nullptr;
|
|
51
|
+
|
|
40
52
|
Polyline *polyline = static_cast<Polyline*>(this);
|
|
41
53
|
PartPool& pool = polyline->tile->cluster->parts_pool;
|
|
42
54
|
Part *current_part = nullptr;
|
|
@@ -65,54 +77,42 @@ void Partitionable::partition()
|
|
|
65
77
|
}
|
|
66
78
|
this->add_part(current_part);
|
|
67
79
|
|
|
68
|
-
this->
|
|
80
|
+
this->transmute_parts();
|
|
69
81
|
}
|
|
70
82
|
|
|
71
|
-
void Partitionable::
|
|
83
|
+
void Partitionable::transmute_transposed_part(Part* part) {
|
|
84
|
+
if (Part* current_seam = this->first_seam; current_seam != nullptr) {
|
|
85
|
+
while (current_seam != nullptr) {
|
|
86
|
+
if (current_seam != part) {
|
|
87
|
+
if (part->within(current_seam)) {
|
|
88
|
+
if (!part->same_length(current_seam)) {
|
|
89
|
+
part->type = Part::EXCLUSIVE;
|
|
90
|
+
part->trasmuted = true;
|
|
91
|
+
std::vector<Queueable<Point>*>& a = static_cast<Position*>(part->head)->end_point()->queues();
|
|
92
|
+
a.erase(std::remove(a.begin(), a.end(), part), a.end());
|
|
93
|
+
std::vector<Queueable<Point>*>& b = static_cast<Position*>(part->tail)->end_point()->queues();
|
|
94
|
+
b.erase(std::remove(b.begin(), b.end(), part), b.end());
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
current_seam = current_seam->next_seam;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
void Partitionable::transmute_parts()
|
|
72
104
|
{ Polyline *polyline = static_cast<Polyline*>(this);
|
|
73
105
|
bool transpose = polyline->tile->cluster->finder()->transpose();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!inside->is(Part::SEAM)) continue;
|
|
77
|
-
for (Part* inside_compare : parts_) {
|
|
78
|
-
if (inside == inside_compare || !inside_compare->is(Part::SEAM)) continue;
|
|
79
|
-
|
|
106
|
+
if (Part* current_seam = this->first_seam; current_seam != nullptr) {
|
|
107
|
+
while (current_seam != nullptr) {
|
|
80
108
|
if (transpose) {
|
|
81
|
-
|
|
82
|
-
Part* target_part;
|
|
83
|
-
if (!inside->same_length(inside_compare)) {
|
|
84
|
-
target_part = inside;
|
|
85
|
-
target_part->type = Part::EXCLUSIVE;
|
|
86
|
-
target_part->trasmuted = true;
|
|
87
|
-
std::vector<Queueable<Point>*>& a = static_cast<Position*>(target_part->head)->end_point()->queues();
|
|
88
|
-
a.erase(std::remove(a.begin(), a.end(), target_part), a.end());
|
|
89
|
-
std::vector<Queueable<Point>*>& b = static_cast<Position*>(target_part->tail)->end_point()->queues();
|
|
90
|
-
b.erase(std::remove(b.begin(), b.end(), target_part), b.end());
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
109
|
+
transmute_transposed_part(current_seam);
|
|
94
110
|
} else {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
Position *position = static_cast<Position*>(pos);
|
|
98
|
-
if (position->end_point()->queues_include(inside_compare))
|
|
99
|
-
{ count++;
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
return false;
|
|
103
|
-
});
|
|
104
|
-
if (count == inside->size) {
|
|
105
|
-
if (count < inside_compare->size) {
|
|
106
|
-
inside->type = Part::EXCLUSIVE;
|
|
107
|
-
inside->trasmuted = true;
|
|
108
|
-
break;
|
|
109
|
-
} else if ( count == inside_compare->size &&
|
|
110
|
-
inside->next == nullptr &&
|
|
111
|
-
inside_compare->prev == nullptr) {
|
|
112
|
-
inside->mirror = true;
|
|
113
|
-
}
|
|
111
|
+
if (!current_seam->transmutation_skip) {
|
|
112
|
+
current_seam->try_transmutation();
|
|
114
113
|
}
|
|
115
114
|
}
|
|
115
|
+
current_seam = current_seam->next_seam;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
}
|
|
@@ -21,8 +21,10 @@ class Partitionable {
|
|
|
21
21
|
|
|
22
22
|
protected:
|
|
23
23
|
std::vector<Part*> parts_;
|
|
24
|
-
|
|
24
|
+
Part* first_seam = nullptr;
|
|
25
|
+
Part* last_seam = nullptr;
|
|
25
26
|
private:
|
|
26
27
|
void add_part(Part* new_part);
|
|
27
|
-
void
|
|
28
|
+
void transmute_parts();
|
|
29
|
+
void transmute_transposed_part(Part* part);
|
|
28
30
|
};
|
|
@@ -102,9 +102,6 @@ module Contrek
|
|
|
102
102
|
if act_part.is?(Part::EXCLUSIVE)
|
|
103
103
|
return if act_part.size == 0
|
|
104
104
|
while (position = act_part.next_position)
|
|
105
|
-
return if outer_joined_polyline.size > 1 &&
|
|
106
|
-
outer_joined_polyline.head.payload == position.payload &&
|
|
107
|
-
act_part == all_parts.first
|
|
108
105
|
outer_joined_polyline.add(position)
|
|
109
106
|
end
|
|
110
107
|
else
|
|
@@ -10,11 +10,13 @@ module Contrek
|
|
|
10
10
|
ADDED = 2
|
|
11
11
|
|
|
12
12
|
attr_reader :polyline, :touched
|
|
13
|
-
attr_accessor :next, :circular_next, :prev, :type, :dead_end, :inverts,
|
|
13
|
+
attr_accessor :next, :circular_next, :prev, :type, :dead_end, :inverts,
|
|
14
|
+
:trasmuted, :versus, :mirror, :next_seam, :transmutation_skip
|
|
14
15
|
def initialize(type, polyline)
|
|
15
16
|
@type = type
|
|
16
17
|
@polyline = polyline
|
|
17
18
|
@next = nil
|
|
19
|
+
@next_seam = nil
|
|
18
20
|
@circular_next = nil
|
|
19
21
|
@prev = nil
|
|
20
22
|
@dead_end = false
|
|
@@ -23,6 +25,7 @@ module Contrek
|
|
|
23
25
|
@trasmuted = false
|
|
24
26
|
@versus = 0
|
|
25
27
|
@mirror = false
|
|
28
|
+
@transmutation_skip = false
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
def is?(type)
|
|
@@ -80,6 +83,27 @@ module Contrek
|
|
|
80
83
|
end
|
|
81
84
|
end
|
|
82
85
|
|
|
86
|
+
def try_transmutation!
|
|
87
|
+
head_queues = head.end_point.queues
|
|
88
|
+
return if head_queues.size == 1
|
|
89
|
+
other_head_part = head_queues.find { |part| part.polyline.tile == polyline.tile && part != self }
|
|
90
|
+
if other_head_part
|
|
91
|
+
tail_queues = tail.end_point.queues
|
|
92
|
+
if tail_queues.find { |part| part == other_head_part }
|
|
93
|
+
if (other_head_part.tail.payload[:y] == tail.payload[:y] && other_head_part.head.payload[:y] == head.payload[:y]) ||
|
|
94
|
+
(other_head_part.tail.payload[:y] == head.payload[:y] && other_head_part.head.payload[:y] == tail.payload[:y])
|
|
95
|
+
if self.next.nil? && other_head_part.prev.nil?
|
|
96
|
+
self.mirror = true
|
|
97
|
+
end
|
|
98
|
+
else
|
|
99
|
+
self.type = Part::EXCLUSIVE
|
|
100
|
+
self.trasmuted = true
|
|
101
|
+
other_head_part.transmutation_skip = true
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
83
107
|
def within?(other)
|
|
84
108
|
self_min, self_max = [head.payload[:y], tail.payload[:y]].minmax
|
|
85
109
|
other_min, other_max = [other.head.payload[:y], other.tail.payload[:y]].minmax
|
|
@@ -8,6 +8,8 @@ module Contrek
|
|
|
8
8
|
def initialize(*args, **kwargs, &block)
|
|
9
9
|
super
|
|
10
10
|
@parts = []
|
|
11
|
+
@first_seam = nil
|
|
12
|
+
@last_seam = nil
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def add_part(new_part)
|
|
@@ -16,8 +18,14 @@ module Contrek
|
|
|
16
18
|
last.next = last.circular_next = new_part if last
|
|
17
19
|
new_part.circular_next = @parts.first
|
|
18
20
|
new_part.prev = last
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
if new_part.is?(Part::SEAM)
|
|
22
|
+
@first_seam ||= new_part
|
|
23
|
+
if !@last_seam.nil?
|
|
24
|
+
@last_seam.next_seam = new_part
|
|
25
|
+
end
|
|
26
|
+
@last_seam = new_part
|
|
27
|
+
new_part.orient!
|
|
28
|
+
end
|
|
21
29
|
end
|
|
22
30
|
|
|
23
31
|
def inspect_parts
|
|
@@ -27,6 +35,8 @@ module Contrek
|
|
|
27
35
|
def partition!
|
|
28
36
|
current_part = nil
|
|
29
37
|
@parts = []
|
|
38
|
+
@first_seam = nil
|
|
39
|
+
@last_seam = nil
|
|
30
40
|
|
|
31
41
|
@raw.each_with_index do |position, n|
|
|
32
42
|
if @tile.tg_border?(position)
|
|
@@ -49,7 +59,7 @@ module Contrek
|
|
|
49
59
|
end
|
|
50
60
|
add_part(current_part)
|
|
51
61
|
|
|
52
|
-
|
|
62
|
+
transmute_parts!
|
|
53
63
|
end
|
|
54
64
|
|
|
55
65
|
private
|
|
@@ -57,42 +67,37 @@ module Contrek
|
|
|
57
67
|
# If there are SEAM parts and one is canceled out by another within the same polyline,
|
|
58
68
|
# meaning that all its points are repeated in another, longer sequence,
|
|
59
69
|
# then the shorter one is converted to EXCLUSIVE and marked as transmuted
|
|
60
|
-
def
|
|
70
|
+
def transmute_parts!
|
|
61
71
|
transpose = tile.cluster.finder.transpose?
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
next unless inside.is?(Part::SEAM)
|
|
65
|
-
@parts.each do |inside_compare|
|
|
66
|
-
next if inside == inside_compare
|
|
67
|
-
next unless inside_compare.is?(Part::SEAM)
|
|
68
|
-
|
|
72
|
+
if (current_seam = @first_seam)
|
|
73
|
+
loop do
|
|
69
74
|
if transpose
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if count == inside_compare.size && inside.next.nil? && inside_compare.prev.nil?
|
|
92
|
-
inside.mirror = true
|
|
75
|
+
transmute_transposed_part(current_seam)
|
|
76
|
+
elsif !current_seam.transmutation_skip
|
|
77
|
+
current_seam.try_transmutation!
|
|
78
|
+
end
|
|
79
|
+
current_seam = current_seam.next_seam
|
|
80
|
+
break if current_seam.nil?
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def transmute_transposed_part(part)
|
|
86
|
+
if (current_seam = @first_seam)
|
|
87
|
+
loop do
|
|
88
|
+
if current_seam != part
|
|
89
|
+
if part.within?(current_seam)
|
|
90
|
+
if !part.same_length?(current_seam)
|
|
91
|
+
part.type = Part::EXCLUSIVE
|
|
92
|
+
part.trasmuted = true
|
|
93
|
+
part.head.end_point.queues.delete(part)
|
|
94
|
+
part.tail.end_point.queues.delete(part)
|
|
95
|
+
return
|
|
93
96
|
end
|
|
94
97
|
end
|
|
95
98
|
end
|
|
99
|
+
current_seam = current_seam.next_seam
|
|
100
|
+
break if current_seam.nil?
|
|
96
101
|
end
|
|
97
102
|
end
|
|
98
103
|
end
|
data/lib/contrek/version.rb
CHANGED
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.3.
|
|
4
|
+
version: 1.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emanuele Cesaroni
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec
|
|
@@ -307,7 +307,7 @@ metadata:
|
|
|
307
307
|
homepage_uri: https://github.com/runout77/contrek
|
|
308
308
|
documentation_uri: https://github.com/runout77/contrek#readme
|
|
309
309
|
changelog_uri: https://github.com/runout77/contrek/blob/main/CHANGELOG.md
|
|
310
|
-
post_install_message:
|
|
310
|
+
post_install_message:
|
|
311
311
|
rdoc_options: []
|
|
312
312
|
require_paths:
|
|
313
313
|
- lib
|
|
@@ -323,7 +323,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
323
323
|
version: '0'
|
|
324
324
|
requirements: []
|
|
325
325
|
rubygems_version: 3.5.22
|
|
326
|
-
signing_key:
|
|
326
|
+
signing_key:
|
|
327
327
|
specification_version: 4
|
|
328
328
|
summary: Fast PNG contour tracing and shape detection for Ruby
|
|
329
329
|
test_files: []
|