contrek 1.3.3 → 1.3.4

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/Gemfile.lock +3 -1
  4. data/README.md +28 -28
  5. data/contrek.gemspec +1 -0
  6. data/ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h +2 -0
  7. data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +0 -2
  8. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +11 -5
  9. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +8 -10
  10. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +4 -1
  11. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +114 -42
  12. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +1 -1
  13. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +2 -1
  14. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +1 -1
  15. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +66 -116
  16. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.h +1 -1
  17. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +1 -0
  18. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +2 -87
  19. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +1 -13
  20. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/PartPool.cpp +0 -1
  21. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +1 -57
  22. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.h +1 -4
  23. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queueable.h +31 -17
  24. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.cpp +0 -16
  25. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.h +0 -9
  26. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +1 -1
  27. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.cpp +177 -0
  28. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.h +52 -0
  29. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/RasterReducer.cpp +109 -0
  30. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/RasterReducer.h +32 -0
  31. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/VisvalingamReducer.h +6 -1
  32. data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +4 -0
  33. data/lib/contrek/bitmaps/painting.rb +27 -10
  34. data/lib/contrek/bitmaps/png_bitmap.rb +3 -0
  35. data/lib/contrek/bitmaps/raw_bitmap.rb +1 -0
  36. data/lib/contrek/bitmaps/rendering.rb +74 -0
  37. data/lib/contrek/finder/concurrent/cursor.rb +62 -84
  38. data/lib/contrek/finder/concurrent/finder.rb +2 -1
  39. data/lib/contrek/finder/concurrent/part.rb +4 -77
  40. data/lib/contrek/finder/concurrent/partitionable.rb +1 -58
  41. data/lib/contrek/finder/concurrent/queueable.rb +23 -5
  42. data/lib/contrek/finder/concurrent/sequence.rb +0 -15
  43. data/lib/contrek/finder/concurrent/tile.rb +1 -1
  44. data/lib/contrek/finder/node.rb +26 -8
  45. data/lib/contrek/finder/node_cluster.rb +91 -37
  46. data/lib/contrek/finder/polygon_finder.rb +1 -1
  47. data/lib/contrek/reducers/douglas_peucker_reducer.rb +191 -0
  48. data/lib/contrek/reducers/raster_reducer.rb +72 -0
  49. data/lib/contrek/version.rb +1 -1
  50. data/lib/contrek.rb +3 -0
  51. metadata +23 -2
@@ -0,0 +1,177 @@
1
+ /*
2
+ * DouglasPeuckerReducer.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 "DouglasPeuckerReducer.h"
11
+ #include <cstdint>
12
+ #include <limits>
13
+ #include <vector>
14
+
15
+ DouglasPeuckerReducer::DouglasPeuckerReducer(std::vector<Point>& list_of_points)
16
+ : Reducer(list_of_points) {
17
+ }
18
+
19
+ void DouglasPeuckerReducer::reduce() {
20
+ const std::size_t count = points.size();
21
+ if (count < 4) {
22
+ return;
23
+ }
24
+ const std::size_t first_index = extremeIndexByX();
25
+ const std::size_t second_index = farthestIndexFrom(first_index);
26
+ if (second_index == first_index) {
27
+ return;
28
+ }
29
+ std::vector<bool> keep(count, false);
30
+ keep[first_index] = true;
31
+ keep[second_index] = true;
32
+ simplifyArc(first_index, second_index, keep);
33
+ simplifyArc(second_index, first_index, keep);
34
+ compactRing(keep);
35
+ }
36
+
37
+ std::size_t DouglasPeuckerReducer::extremeIndexByX() const {
38
+ std::size_t best = 0;
39
+ int best_x = points[0].x;
40
+ for (std::size_t index = 1; index < points.size(); ++index) {
41
+ const int x = points[index].x;
42
+ if (x < best_x) {
43
+ best_x = x;
44
+ best = index;
45
+ }
46
+ }
47
+ return best;
48
+ }
49
+
50
+ std::size_t DouglasPeuckerReducer::farthestIndexFrom(std::size_t origin_index) const {
51
+ const Point& origin = points[origin_index];
52
+ std::size_t best_index = origin_index;
53
+ std::int64_t best_distance = -1;
54
+ for (std::size_t index = 0; index < points.size(); ++index) {
55
+ const std::int64_t dx = static_cast<std::int64_t>(points[index].x) - origin.x;
56
+ const std::int64_t dy = static_cast<std::int64_t>(points[index].y) - origin.y;
57
+ const std::int64_t distance = dx * dx + dy * dy;
58
+ if (distance > best_distance) {
59
+ best_distance = distance;
60
+ best_index = index;
61
+ }
62
+ }
63
+ return best_index;
64
+ }
65
+
66
+ void DouglasPeuckerReducer::simplifyArc(std::size_t first_index, std::size_t last_index, std::vector<bool>& keep) const {
67
+ const std::size_t count = points.size();
68
+ std::vector<Arc> stack;
69
+ stack.push_back({first_index, last_index});
70
+ while (!stack.empty()) {
71
+ const Arc arc = stack.back();
72
+ stack.pop_back();
73
+ std::size_t split_index = 0;
74
+ if (!farthestOnArc(
75
+ arc.first,
76
+ arc.last,
77
+ count,
78
+ split_index)) {
79
+ continue;
80
+ }
81
+ keep[split_index] = true;
82
+ if (nextIndex(arc.first, count) != split_index) {
83
+ stack.push_back({arc.first, split_index});
84
+ }
85
+ if (nextIndex(split_index, count) != arc.last) {
86
+ stack.push_back({split_index, arc.last});
87
+ }
88
+ }
89
+ }
90
+
91
+ bool DouglasPeuckerReducer::farthestOnArc(std::size_t first_index, std::size_t last_index,
92
+ std::size_t count, std::size_t& split_index) const {
93
+ const Point& first = points[first_index];
94
+ const Point& last = points[last_index];
95
+ const double ax = static_cast<double>(first.x);
96
+ const double ay = static_cast<double>(first.y);
97
+ const double bx = static_cast<double>(last.x);
98
+ const double by = static_cast<double>(last.y);
99
+ const double abx = bx - ax;
100
+ const double aby = by - ay;
101
+ const double length_squared = abx * abx + aby * aby;
102
+ double max_distance = TOLERANCE_SQUARED;
103
+ bool found = false;
104
+ std::size_t index = nextIndex(first_index, count);
105
+
106
+ while (index != last_index) {
107
+ const Point& point = points[index];
108
+ double distance;
109
+ if (length_squared == 0.0) {
110
+ const double dx = static_cast<double>(point.x) - ax;
111
+ const double dy = static_cast<double>(point.y) - ay;
112
+ distance = dx * dx + dy * dy;
113
+ } else {
114
+ distance = pointSegmentDistanceSquared(
115
+ static_cast<double>(point.x),
116
+ static_cast<double>(point.y),
117
+ ax,
118
+ ay,
119
+ bx,
120
+ by,
121
+ abx,
122
+ aby,
123
+ length_squared);
124
+ }
125
+ if (distance > max_distance) {
126
+ max_distance = distance;
127
+ split_index = index;
128
+ found = true;
129
+ }
130
+ index = nextIndex(index, count);
131
+ }
132
+ return found;
133
+ }
134
+
135
+ double DouglasPeuckerReducer::pointSegmentDistanceSquared(
136
+ double px,
137
+ double py,
138
+ double ax,
139
+ double ay,
140
+ double bx,
141
+ double by,
142
+ double abx,
143
+ double aby,
144
+ double length_squared) {
145
+ const double apx = px - ax;
146
+ const double apy = py - ay;
147
+ const double dot = apx * abx + apy * aby;
148
+ if (dot <= 0.0) {
149
+ return apx * apx + apy * apy;
150
+ }
151
+ if (dot >= length_squared) {
152
+ const double bpx = px - bx;
153
+ const double bpy = py - by;
154
+ return bpx * bpx + bpy * bpy;
155
+ }
156
+ const double cross = apx * aby - apy * abx;
157
+ return (cross * cross) / length_squared;
158
+ }
159
+
160
+ std::size_t DouglasPeuckerReducer::nextIndex(std::size_t index, std::size_t count) {
161
+ return index + 1 == count ? 0 : index + 1;
162
+ }
163
+
164
+ void DouglasPeuckerReducer::compactRing(const std::vector<bool>& keep) {
165
+ std::size_t write_index = 0;
166
+ for (
167
+ std::size_t read_index = 0;
168
+ read_index < points.size();
169
+ ++read_index
170
+ ) {
171
+ if (keep[read_index]) {
172
+ points[write_index] = points[read_index];
173
+ ++write_index;
174
+ }
175
+ }
176
+ points.resize(write_index);
177
+ }
@@ -0,0 +1,52 @@
1
+ /*
2
+ * DouglasPeuckerReducer.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 <cstddef>
12
+ #include <vector>
13
+ #include "Reducer.h"
14
+
15
+ struct Point;
16
+
17
+ class DouglasPeuckerReducer : public Reducer {
18
+ public:
19
+ explicit DouglasPeuckerReducer(std::vector<Point>& list_of_points);
20
+ void reduce() override;
21
+
22
+ private:
23
+ static constexpr double TOLERANCE_SQUARED = 1.0;
24
+ struct Arc {
25
+ std::size_t first;
26
+ std::size_t last;
27
+ };
28
+ std::size_t extremeIndexByX() const;
29
+ std::size_t farthestIndexFrom(
30
+ std::size_t origin_index) const;
31
+ void simplifyArc(
32
+ std::size_t first_index,
33
+ std::size_t last_index,
34
+ std::vector<bool>& keep) const;
35
+ bool farthestOnArc(
36
+ std::size_t first_index,
37
+ std::size_t last_index,
38
+ std::size_t count,
39
+ std::size_t& split_index) const;
40
+ static double pointSegmentDistanceSquared(
41
+ double px,
42
+ double py,
43
+ double ax,
44
+ double ay,
45
+ double bx,
46
+ double by,
47
+ double abx,
48
+ double aby,
49
+ double length_squared);
50
+ static std::size_t nextIndex(std::size_t index, std::size_t count);
51
+ void compactRing(const std::vector<bool>& keep);
52
+ };
@@ -0,0 +1,109 @@
1
+ /*
2
+ * RasterReducer.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 "RasterReducer.h"
11
+ #include <stdexcept>
12
+ #include <string>
13
+
14
+ RasterReducer::RasterReducer(std::vector<Point>& points, int versus)
15
+ : Reducer(points),
16
+ versus_(versus) {
17
+ }
18
+
19
+ void RasterReducer::reduce() {
20
+ toRasterPolygon(points, versus_);
21
+ }
22
+
23
+ void RasterReducer::toRasterPolygon(std::vector<Point>& points, int versus) {
24
+ const std::size_t n = points.size();
25
+
26
+ if (n == 0) {
27
+ return;
28
+ }
29
+
30
+ const bool outer = versus == Node::O;
31
+ const int firstX = points.front().x;
32
+ const int firstY = points.front().y;
33
+ int previousX = points.back().x;
34
+ int previousY = points.back().y;
35
+
36
+ for (std::size_t i = 0; i < n; ++i) {
37
+ const int currentX = points[i].x;
38
+ const int currentY = points[i].y;
39
+ int nextX;
40
+ int nextY;
41
+ if (i + 1 < n) {
42
+ nextX = points[i + 1].x;
43
+ nextY = points[i + 1].y;
44
+ } else {
45
+ nextX = firstX;
46
+ nextY = firstY;
47
+ }
48
+ const Pole incomingSide = segmentPole(
49
+ currentX - previousX,
50
+ currentY - previousY,
51
+ outer);
52
+ const Pole outgoingSide = segmentPole(
53
+ nextX - currentX,
54
+ nextY - currentY,
55
+ outer);
56
+ int x = currentX;
57
+ int y = currentY;
58
+ const auto applySide = [&x, &y](Pole side) {
59
+ switch (side) {
60
+ case Pole::South:
61
+ --y;
62
+ break;
63
+
64
+ case Pole::East:
65
+ --x;
66
+ break;
67
+
68
+ case Pole::North:
69
+ case Pole::West:
70
+ break;
71
+ }
72
+ };
73
+ applySide(incomingSide);
74
+ if (outgoingSide != incomingSide) {
75
+ applySide(outgoingSide);
76
+ }
77
+ points[i].x = x;
78
+ points[i].y = y;
79
+ previousX = currentX;
80
+ previousY = currentY;
81
+ }
82
+ }
83
+
84
+ RasterReducer::Pole RasterReducer::segmentPole(int dx, int dy, bool clockwise) {
85
+ const int sx = sign(dx);
86
+ const int sy = sign(dy);
87
+ if (sx == 0 && sy == 1) {
88
+ return clockwise ? Pole::East : Pole::West;
89
+ }
90
+ if (sx == 1 && sy == 0) {
91
+ return clockwise ? Pole::North : Pole::South;
92
+ }
93
+ if (sx == 0 && sy == -1) {
94
+ return clockwise ? Pole::West : Pole::East;
95
+ }
96
+ if (sx == -1 && sy == 0) {
97
+ return clockwise ? Pole::South : Pole::North;
98
+ }
99
+ throw std::invalid_argument(
100
+ "Non-axial segment dx=" +
101
+ std::to_string(dx) +
102
+ " dy=" +
103
+ std::to_string(dy) +
104
+ "!");
105
+ }
106
+
107
+ int RasterReducer::sign(int value) {
108
+ return (value > 0) - (value < 0);
109
+ }
@@ -0,0 +1,32 @@
1
+ /*
2
+ * RasterReducer.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 "Reducer.h"
13
+
14
+ struct Point;
15
+
16
+ class RasterReducer : public Reducer {
17
+ public:
18
+ RasterReducer(std::vector<Point>& points, int versus);
19
+ void reduce() override;
20
+
21
+ private:
22
+ enum class Pole {
23
+ North,
24
+ South,
25
+ East,
26
+ West
27
+ };
28
+ int versus_;
29
+ void toRasterPolygon(std::vector<Point>& points, int versus);
30
+ static Pole segmentPole(int dx, int dy, bool clockwise);
31
+ static int sign(int value);
32
+ };
@@ -25,7 +25,12 @@ class VisvalingamReducer : public Reducer {
25
25
  class Triangle {
26
26
  public:
27
27
  static int area(const Point& a, const Point& b, const Point& c) {
28
- return std::abs(((c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y)) / 2);
28
+ const int cross = (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y);
29
+ int half = cross / 2;
30
+ if (cross < 0 && cross % 2 != 0) {
31
+ --half;
32
+ }
33
+ return std::abs(half);
29
34
  }
30
35
  };
31
36
 
@@ -52,6 +52,10 @@
52
52
  #include "PolygonFinder/src/polygon/reducers/LinearReducer.h"
53
53
  #include "PolygonFinder/src/polygon/reducers/VisvalingamReducer.cpp"
54
54
  #include "PolygonFinder/src/polygon/reducers/VisvalingamReducer.h"
55
+ #include "PolygonFinder/src/polygon/reducers/RasterReducer.cpp"
56
+ #include "PolygonFinder/src/polygon/reducers/RasterReducer.h"
57
+ #include "PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.cpp"
58
+ #include "PolygonFinder/src/polygon/reducers/DouglasPeuckerReducer.h"
55
59
  #include "PolygonFinder/src/polygon/finder/concurrent/Finder.h"
56
60
  #include "PolygonFinder/src/polygon/finder/concurrent/Finder.cpp"
57
61
  #include "PolygonFinder/src/polygon/finder/concurrent/Poolable.h"
@@ -21,23 +21,40 @@ module Contrek
21
21
  colors.sort_by { |color, count| -color }
22
22
  end
23
23
 
24
- def self.direct_draw_polygons(polygons, png_image)
24
+ def self.direct_draw_polygons(polygons, png_image, zoom: 1)
25
25
  polygons.compact.each do |poly|
26
- color = ChunkyPNG::Color("red @ 1.0")
27
- poly[:outer].each_cons(2) do |coords|
28
- png_image.draw_line(coords[0][:x], coords[0][:y], coords[1][:x], coords[1][:y], color)
26
+ if (bounds = poly[:bounds])
27
+ b_factor = 3.to_f / zoom
28
+ bounds_polygon = [
29
+ {x: bounds[:min_x] - b_factor, y: bounds[:min_y] - b_factor},
30
+ {x: bounds[:max_x] + b_factor, y: bounds[:min_y] - b_factor},
31
+ {x: (bounds[:max_x] + b_factor), y: bounds[:max_y] + b_factor},
32
+ {x: (bounds[:min_x] - b_factor), y: bounds[:max_y] + b_factor}
33
+ ]
34
+ draw_polygon(bounds_polygon, png_image, ChunkyPNG::Color("blue @ 1.0"), zoom)
29
35
  end
30
- png_image.draw_line(poly[:outer][0][:x], poly[:outer][0][:y], poly[:outer][-1][:x], poly[:outer][-1][:y], color)
36
+ color = ChunkyPNG::Color("red @ 1.0")
37
+ draw_polygon(poly[:outer], png_image, color, zoom)
31
38
  color = ChunkyPNG::Color("green @ 1.0")
32
39
  poly[:inner].each do |sequence|
33
- next if sequence.empty?
34
- sequence.each_cons(2) do |coords|
35
- png_image.draw_line(coords[0][:x], coords[0][:y], coords[1][:x], coords[1][:y], color)
36
- end
37
- png_image.draw_line(sequence[0][:x], sequence[0][:y], sequence[-1][:x], sequence[-1][:y], color)
40
+ draw_polygon(sequence, png_image, color, zoom)
38
41
  end
39
42
  end
40
43
  end
44
+
45
+ def self.draw_polygon(sequence, png_image, color, zoom, margin = 0)
46
+ return if sequence.empty?
47
+ sequence.each_cons(2) do |coords|
48
+ png_image.draw_line(coords[0][:x] * zoom,
49
+ coords[0][:y] * zoom,
50
+ coords[1][:x] * zoom,
51
+ coords[1][:y] * zoom, color)
52
+ end
53
+ png_image.draw_line(sequence[0][:x] * zoom,
54
+ sequence[0][:y] * zoom,
55
+ sequence[-1][:x] * zoom,
56
+ sequence[-1][:y] * zoom, color)
57
+ end
41
58
  end
42
59
  end
43
60
  end
@@ -5,6 +5,9 @@ require "chunky_png"
5
5
  module Contrek
6
6
  module Bitmaps
7
7
  class PngBitmap < Bitmap
8
+ include Rendering
9
+
10
+ attr_reader :image
8
11
  def initialize(file_path)
9
12
  @image = ChunkyPNG::Image.from_file(file_path)
10
13
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ruby2d"
3
4
  module Contrek
4
5
  module Bitmaps
5
6
  class RawBitmap < PngBitmap
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Contrek
4
+ module Bitmaps
5
+ module Rendering
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def to_window(result, zoom: 100, coords: true, window_w: 2000, window_h: 1200, font_h: 20)
12
+ ubuntu_purple = ChunkyPNG::Color.from_hex("#77216F")
13
+ bm = RawBitmap.new(w: window_w, h: window_h, color: ubuntu_purple)
14
+ # grid
15
+ light_purple = ChunkyPNG::Color.from_hex("#95338B")
16
+ (window_w / zoom).times { |x| bm.draw_line(x * zoom, 0, x * zoom, window_h, light_purple) }
17
+ (window_h / zoom).times { |y| bm.draw_line(0, y * zoom, window_w, y * zoom, light_purple) }
18
+ # polygons
19
+ Painting.direct_draw_polygons(result.points, bm, zoom: zoom)
20
+ # windowing
21
+ Tempfile.create do |temp_file|
22
+ bm.image.save(temp_file.path, color_mode: ChunkyPNG::COLOR_TRUECOLOR_ALPHA)
23
+ Window.clear
24
+ Window.set(
25
+ title: "Rendering #{result[:metadata][:groups]} polygons",
26
+ width: window_w,
27
+ height: window_h,
28
+ resizable: true
29
+ )
30
+ Image.new(
31
+ temp_file.path,
32
+ x: 0, y: 0,
33
+ width: window_w,
34
+ height: window_h
35
+ )
36
+ draw_labels(result.points, zoom: zoom, draw_coords: coords, font_h:)
37
+ Window.on :key_down do |event|
38
+ if event.key == "escape"
39
+ Window.close
40
+ end
41
+ if event.key == "s"
42
+ Window.screenshot("window_content.png")
43
+ end
44
+ end
45
+ Window.show
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def draw_labels(polygons, font_h:, zoom: 1.0, draw_coords: true)
52
+ positions = []
53
+ polygons.compact.each do |poly|
54
+ ([poly[:outer]] + poly[:inner]).each do |seq|
55
+ seq.each_with_index do |coords, n|
56
+ x = coords[:x] * zoom
57
+ y = coords[:y] * zoom
58
+ y += font_h + 2 if positions.index([x, y])
59
+ t = (n + 1).to_s
60
+ t += " - [#{coords[:x]},#{coords[:y]}]" if draw_coords
61
+ draw_text(x, y, t, font_h) unless draw_coords.nil?
62
+ positions << [x, y]
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ def draw_text(x, y, text, font_h)
69
+ Text.new(text, x:, y:, size: font_h, color: "white", z: 10)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end