contrek 1.0.5 → 1.0.7
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/.gitignore +4 -3
- data/CHANGELOG.md +25 -0
- data/Gemfile.lock +1 -1
- data/README.md +133 -33
- data/Rakefile +3 -0
- data/contrek.gemspec +6 -3
- data/ext/cpp_polygon_finder/PolygonFinder/Makefile +44 -0
- data/ext/cpp_polygon_finder/PolygonFinder/images/sample_10240x10240.png +0 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/Main.cpp +14 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +136 -15
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.h +5 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/CpuTimer.h +44 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/Bitmap.cpp +8 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/Bitmap.h +3 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.cpp +63 -573
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.h +17 -18
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RemoteFastPngBitmap.cpp +22 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RemoteFastPngBitmap.h +4 -8
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/spng.c +6980 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/spng.h +537 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +101 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.h +16 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/List.cpp +0 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/List.h +4 -7
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Lists.cpp +13 -13
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Lists.h +5 -7
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +47 -41
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +15 -10
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +181 -178
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +19 -20
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Polygon.h +20 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +52 -137
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +85 -16
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/RectBounds.h +39 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ClippedPolygonFinder.cpp +14 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ClippedPolygonFinder.h +17 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +117 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.h +32 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +344 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.h +46 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/EndPoint.cpp +14 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/EndPoint.h +22 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/FakeCluster.cpp +13 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/FakeCluster.h +17 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +139 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.h +52 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Hub.cpp +23 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Hub.h +40 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +70 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +47 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/PartPool.cpp +24 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/PartPool.h +23 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +182 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.h +30 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +108 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.h +52 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Poolable.cpp +59 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Poolable.h +52 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Position.cpp +31 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Position.h +25 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queue.h +36 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queueable.h +230 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.cpp +35 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.h +20 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.cpp +26 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.h +23 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +105 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.h +56 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/Matcher.cpp +3 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/Matcher.h +5 -8
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/RGBMatcher.h +3 -7
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/RGBNotMatcher.cpp +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/RGBNotMatcher.h +4 -8
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/ValueNotMatcher.cpp +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/matchers/ValueNotMatcher.h +3 -7
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/LinearReducer.cpp +23 -15
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/LinearReducer.h +6 -8
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/Reducer.cpp +2 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/Reducer.h +4 -8
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/UniqReducer.cpp +9 -12
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/UniqReducer.h +3 -7
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/VisvalingamReducer.cpp +26 -27
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/VisvalingamReducer.h +76 -87
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +167 -138
- data/ext/cpp_polygon_finder/extconf.rb +14 -0
- data/lib/contrek/bitmaps/sample_generator.rb +56 -0
- data/lib/contrek/cpp/cpp_concurrent_finder.rb +9 -0
- data/lib/contrek/finder/bounds.rb +17 -0
- data/lib/contrek/finder/concurrent/cluster.rb +2 -2
- data/lib/contrek/finder/concurrent/cursor.rb +8 -8
- data/lib/contrek/finder/concurrent/finder.rb +19 -15
- data/lib/contrek/finder/concurrent/part.rb +2 -2
- data/lib/contrek/finder/concurrent/partitionable.rb +1 -1
- data/lib/contrek/finder/concurrent/polyline.rb +17 -15
- data/lib/contrek/finder/concurrent/sequence.rb +11 -0
- data/lib/contrek/finder/concurrent/tile.rb +3 -3
- data/lib/contrek/finder/node_cluster.rb +16 -8
- data/lib/contrek/finder/polygon_finder.rb +9 -10
- data/lib/contrek/finder/result.rb +13 -0
- data/lib/contrek/results/cpp_result.rb +21 -0
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +11 -1
- metadata +59 -10
- data/ext/cpp_polygon_finder/PolygonFinder/.cproject +0 -136
- data/ext/cpp_polygon_finder/PolygonFinder/.project +0 -27
- data/ext/cpp_polygon_finder/PolygonFinder/.settings/org.eclipse.ltk.core.refactoring.prefs +0 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/PngBitmap.cpp +0 -48
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/PngBitmap.h +0 -32
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d0d1a364ff39a11f632f5f57d99970448173727e8f5a42e84e1b334521c1e1d1
|
|
4
|
+
data.tar.gz: 24a52785c8735e9fce60931a1ffc1bd974aa5a50a6fb755229cd25dd5263cab3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e321b716a5c16d6c75f24bc6bcdebc302a6ea9785009bad93905321ebc7a97ef29048fb1c6e4e0557611ec9d096dee874bd1ff37f66ad2763c4dacd7176b0ce4
|
|
7
|
+
data.tar.gz: 38608a89ed8ee0c43a985bf8057d07b860acd524d2e8c0022651e5ec3fdcc8f13e3d515e309d436ad261203e00a58b901dd6cf141bfbeeb93520f7937ccdb4b8
|
data/.gitignore
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
# rspec failure tracking
|
|
4
4
|
.rspec_status
|
|
5
5
|
|
|
6
|
-
/ext/cpp_polygon_finder/PolygonFinder/
|
|
7
|
-
/ext/cpp_polygon_finder
|
|
8
|
-
/ext/cpp_polygon_finder/
|
|
6
|
+
/ext/cpp_polygon_finder/PolygonFinder/build/
|
|
7
|
+
/ext/cpp_polygon_finder/PolygonFinder/contrek
|
|
8
|
+
/ext/cpp_polygon_finder/PolygonFinder/build/*.o
|
|
9
|
+
/ext/cpp_polygon_finder/PolygonFinder/build/*.d
|
|
9
10
|
/ext/cpp_polygon_finder/Makefile
|
|
10
11
|
/ext/cpp_polygon_finder/cpp_polygon_finder.o
|
|
11
12
|
/ext/cpp_polygon_finder/cpp_polygon_finder.so
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [1.0.1] - 2025-05-18
|
|
6
|
+
### Added
|
|
7
|
+
- The project is made available as a Ruby gem
|
|
8
|
+
|
|
9
|
+
## [1.0.3] - 2025-11-15
|
|
10
|
+
### Changed
|
|
11
|
+
- Minor changes
|
|
12
|
+
|
|
13
|
+
## [1.0.5] - 2025-11-16
|
|
14
|
+
### Added
|
|
15
|
+
- Added ruby side multithreading supports
|
|
16
|
+
|
|
17
|
+
## [1.0.6] - 2026-01-06
|
|
18
|
+
### Added
|
|
19
|
+
- Added C++ multithreading supports
|
|
20
|
+
- Optimized old just present C++ code
|
|
21
|
+
- Removed Png++ dependency in place of libspn
|
|
22
|
+
|
|
23
|
+
## [1.0.7] - 2026-01-10
|
|
24
|
+
### Added
|
|
25
|
+
- Optimized C++/Ruby data transfer: Implemented NumPy-compatible binary serialization for coordinate outputs. This reduces serialization overhead and significantly increases data throughput between the C++ core and the Ruby interface.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Contrek
|
|
2
|
-
Contrek is a Ruby
|
|
2
|
+
Contrek is a Ruby gem with a C++ core for fast contour tracing and edge detection in PNG images. It extracts polygonal contours from bitmap shapes, enabling image processing, shape analysis, and raster-to-vector workflows such as PNG to SVG conversion. Manages png images usign libspng (version 0.7.4) library. May work multithreading.
|
|
3
3
|
|
|
4
4
|
## About Contrek library
|
|
5
|
-
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
|
|
5
|
+
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.
|
|
6
6
|
|
|
7
7
|
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
|
|
8
8
|

|
|
@@ -19,21 +19,7 @@ And then execute:
|
|
|
19
19
|
|
|
20
20
|
bundle install
|
|
21
21
|
|
|
22
|
-
This will install the gem and compile the native extensions.
|
|
23
|
-
`fatal error: png++/png.hpp: No such file or directory`
|
|
24
|
-
|
|
25
|
-
means that you have to install png++ on your system which Contrek C++ code depends on; visit http://download.savannah.nongnu.org/releases/pngpp/
|
|
26
|
-
Grab the lattest file (here 0.2.9)
|
|
27
|
-
Go to download directory. Extract to /usr/src with
|
|
28
|
-
|
|
29
|
-
sudo tar -zxf png++-0.2.9.tar.gz -C /usr/src
|
|
30
|
-
Change directory to
|
|
31
|
-
|
|
32
|
-
cd /usr/src/png++-0.2.9/
|
|
33
|
-
Do make and make install
|
|
34
|
-
|
|
35
|
-
make
|
|
36
|
-
make install
|
|
22
|
+
This will install the gem and compile the native extensions.
|
|
37
23
|
|
|
38
24
|
## Usage
|
|
39
25
|
In this example we are asking to examine any pixel that does not have the red color.
|
|
@@ -48,9 +34,10 @@ result = Contrek.contour!(
|
|
|
48
34
|
}
|
|
49
35
|
)
|
|
50
36
|
```
|
|
51
|
-
The
|
|
37
|
+
The resulting metadata information contains the execution times (microseconds), the count of polygons found and the nesting tree map. You can access polygons coordinates too (see later).
|
|
38
|
+
|
|
52
39
|
```ruby
|
|
53
|
-
{:benchmarks=>{"build_tangs_sequence"=>0.129, "compress"=>0.037, "plot"=>0.198, "scan"=>0.114, "total"=>0.478}, :groups=>2, :named_sequence=>"", :
|
|
40
|
+
{:benchmarks=>{"build_tangs_sequence"=>0.129, "compress"=>0.037, "plot"=>0.198, "scan"=>0.114, "total"=>0.478}, :groups=>2, :named_sequence=>"", :treemap=>[]}
|
|
54
41
|
|
|
55
42
|
```
|
|
56
43
|
|
|
@@ -77,7 +64,7 @@ polygonfinder = CPPPolygonFinder.new(png_bitmap,
|
|
|
77
64
|
{versus: :a, compress: {visvalingam: {tolerance: 1.5}}})
|
|
78
65
|
result = polygonfinder.process_info
|
|
79
66
|
# draws the polygons found
|
|
80
|
-
Contrek::Bitmaps::Painting.direct_draw_polygons(result
|
|
67
|
+
Contrek::Bitmaps::Painting.direct_draw_polygons(result.points, png_image)
|
|
81
68
|
png_image.save('result.png') # => inspect the image to feedback the result
|
|
82
69
|
```
|
|
83
70
|
|
|
@@ -88,8 +75,6 @@ png_bitmap = CPPRemotePngBitMap.new("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcS
|
|
|
88
75
|
|
|
89
76
|
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
77
|
|
|
91
|
-
The actual multithreaded implementation effort is therefore focused on the native C++ component, which is currently under development and is expected to provide substantially higher performance.
|
|
92
|
-
|
|
93
78
|
```ruby
|
|
94
79
|
result = Contrek.contour!(
|
|
95
80
|
png_file_path: "./spec/files/images/rectangle_8x8.png",
|
|
@@ -114,6 +99,116 @@ Regarding multithreading:
|
|
|
114
99
|
|
|
115
100
|
- The treemap option is currently ignored (multithreaded treemap support will be introduced in upcoming revisions).
|
|
116
101
|
|
|
102
|
+
By not declaring native option CPP Multithreading optimized code is used. In the above example a [105 MP image](spec/files/images/sample_10240x10240.png) is examined by 2 threads working on 2 tiles (total compute time about 2 secs).
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
result = Contrek.contour!(
|
|
106
|
+
png_file_path: "./spec/files/images/sample_10240x10240.png",
|
|
107
|
+
options: {
|
|
108
|
+
number_of_threads: 2,
|
|
109
|
+
class: "value_not_matcher",
|
|
110
|
+
color: {r: 255, g: 255, b: 255, a: 255},
|
|
111
|
+
finder: {number_of_tiles: 2, compress: {uniq: true}}
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
puts result.metadata[:benchmarks].inspect
|
|
115
|
+
|
|
116
|
+
{"compress"=>14.596786999999999,
|
|
117
|
+
"init"=>2078.745861,
|
|
118
|
+
"inner"=>1183.143734,
|
|
119
|
+
"outer"=>118.94489599999999,
|
|
120
|
+
"total"=>2093.342648}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Result
|
|
124
|
+
|
|
125
|
+
The result structure contains polygon coordinates and a set of metadata. Polygon coordinates can be accessed via:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
result.polygons
|
|
129
|
+
|
|
130
|
+
[{:outer=>[11, 2, 11, 5, 6, 5, 6, 2],
|
|
131
|
+
:inner=>[[10, 3, 7, 3, 7, 4, 10, 4]]}]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For native classes (C/C++), coordinates are represented as an interleaved NumPy array (`ndarray`) in the form:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
[x0, y0, x1, y1, ...]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
A ruby side helper method could converts this data structure into point-based representations:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
result.points
|
|
144
|
+
|
|
145
|
+
[{:outer=>[{:x=>11, :y=>2}, {:x=>11, :y=>5}, {:x=>6, :y=>5}, {:x=>6, :y=>2}],
|
|
146
|
+
:inner=>[[{:x=>10, :y=>3}, {:x=>7, :y=>3}, {:x=>7, :y=>4}, {:x=>10, :y=>4}]]}]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- `outer` represents a single polygonal sequence
|
|
150
|
+
- `inner` is represented as an array of polygonal sequences (holes)
|
|
151
|
+
|
|
152
|
+
For pure Ruby implementations (`{ native: false }`), coordinates are always expressed as points.
|
|
153
|
+
In this case, both `polygons` and `points` return the same data, represented as hashes with `x` and `y` keys.
|
|
154
|
+
|
|
155
|
+
## Metadata
|
|
156
|
+
|
|
157
|
+
Metadata associated with the result can be accessed via:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
result.metadata
|
|
161
|
+
|
|
162
|
+
{:benchmarks=>{"build_tangs_sequence"=>0.00577,
|
|
163
|
+
"compress"=>0.01239,
|
|
164
|
+
"plot"=>0.01919,
|
|
165
|
+
"scan"=>0.01473,
|
|
166
|
+
"total"=>0.05208},
|
|
167
|
+
:groups=>1,
|
|
168
|
+
:named_sequence=>"AFEDCBA",
|
|
169
|
+
:treemap=>[]}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Treemap
|
|
173
|
+
|
|
174
|
+
The treemap is a data structure that represents the containment hierarchy of polygons. For each polygon, it defines the parent polygon in which it is contained and its relative position among siblings. This structure allows reconstruction of the inclusion tree and determination of nesting relationships between geometries. Consider the above image
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
"AAAAAAAAAAAAAAAAAAAAAA" \
|
|
178
|
+
"A A" \
|
|
179
|
+
"A BBBBBBBBBBBBBBBBBB A" \
|
|
180
|
+
"A BBBBBBBBBBBBBBBBBB A" \
|
|
181
|
+
"A BBB BBBBB BBB A" \
|
|
182
|
+
"A BBB CC BBBBB D BBB A" \
|
|
183
|
+
"A BBB BBBBB BBB A" \
|
|
184
|
+
"A BBBBBBBBBBBBBBBBBB A" \
|
|
185
|
+
"A BBBBBBBBBBBBBBBBBB A" \
|
|
186
|
+
"A A" \
|
|
187
|
+
"AAAAAAAAAAAAAAAAAAAAAA"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
result.metadata[:treemap]
|
|
192
|
+
|
|
193
|
+
[[-1, -1], # A
|
|
194
|
+
[0, 0], # B
|
|
195
|
+
[1, 0], # C
|
|
196
|
+
[1, 1]] # D
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
There are four polygons (`A`, `B`, `C`, and `D`).
|
|
200
|
+
The order matches the one provided in `result.polygons`.
|
|
201
|
+
|
|
202
|
+
Each entry has the structure:
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
[parent_index, position]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- Polygon **A** (index `0`) has no parent and is represented as `[-1, -1]`
|
|
209
|
+
- Polygon **B** is contained in **A** and is its first child → `[0, 0]`
|
|
210
|
+
- Polygon **C** is contained in **B** and is its first child → `[1, 0]`
|
|
211
|
+
- Polygon **D** is also contained in **B** and is its second child → `[1, 1]`
|
|
117
212
|
|
|
118
213
|
## Multithreaded approach
|
|
119
214
|
|
|
@@ -151,22 +246,23 @@ This process is applied recursively, merging bands until a single final band rem
|
|
|
151
246
|
One of the most complex test you can find under the spec folder is named "scans poly 1200x800", scans this [image](spec/files/images/sample_1200x800.png) computing coordinates to draw polygons drawn in this [result](spec/files/stored_samples/sample_1200x800.png).
|
|
152
247
|
On pure ruby implementation kept time
|
|
153
248
|
```ruby
|
|
154
|
-
{ :scan=>
|
|
155
|
-
:build_tangs_sequence=>
|
|
156
|
-
:plot=>
|
|
157
|
-
:compress=>0.
|
|
158
|
-
:total=>
|
|
249
|
+
{ :scan=>801.494,
|
|
250
|
+
:build_tangs_sequence=>160.491,
|
|
251
|
+
:plot=>86.633,
|
|
252
|
+
:compress=>0.002,
|
|
253
|
+
:total=>1048.62}
|
|
159
254
|
```
|
|
160
255
|
This the one for the native C++
|
|
161
256
|
```ruby
|
|
162
|
-
{ :
|
|
163
|
-
:
|
|
164
|
-
:
|
|
165
|
-
:
|
|
166
|
-
:
|
|
257
|
+
{ scan: 7.1146329999999995,
|
|
258
|
+
build_tangs_sequence: 3.063812,
|
|
259
|
+
plot: 4.474851999999999,
|
|
260
|
+
compress: 0.0031999999999999997
|
|
261
|
+
total: 14.656496999999998
|
|
262
|
+
}
|
|
167
263
|
```
|
|
168
264
|
|
|
169
|
-
About
|
|
265
|
+
About 75x faster. Times are in microseconds; system: AMD Ryzen 7 3700X 8-Core Processor (BogoMIPS: 7199,99) on Ubuntu distro.
|
|
170
266
|
|
|
171
267
|
## License
|
|
172
268
|
|
|
@@ -174,6 +270,10 @@ This project is licensed under the terms of the MIT license.
|
|
|
174
270
|
|
|
175
271
|
See [LICENSE.md](LICENSE.md).
|
|
176
272
|
|
|
273
|
+
## Changelog
|
|
274
|
+
|
|
275
|
+
See [CHANGELOG.md](CHANGELOG.md) for a complete list of changes.
|
|
276
|
+
|
|
177
277
|
## History
|
|
178
278
|
The algorithm was originally developed by me in 2018 when I was commissioned to create a Rails web application whose main objective was to census buildings from GoogleMAPS; the end user had to be able to select their home building by clicking its roof on the map which had to be identified as a clickable polygon. The solution was to configure GoogleMAPS to render buildings of a well-defined color (red), and at each refresh of the same to transform the div into an image (html2canvas) then process it server side returning the polygons to be superimposed again on the map. This required very fast polygons determination. Searching for a library for tracing the contours I was not able to find anything better except OpenCV which however seemed to me a very heavy dependency. So I decided to write my algorithm directly in the context of the ROR application. Once perfected, it was already usable but a bit slow in the higher image resolutions. So I decided to write the counterpart in C++, which came out much faster and which I then used as an extension on Ruby by means of Rice.
|
|
179
279
|
|
data/Rakefile
CHANGED
|
@@ -3,8 +3,11 @@ require "rake"
|
|
|
3
3
|
desc "compiles c++ extension"
|
|
4
4
|
task :compile do |t|
|
|
5
5
|
Dir.chdir("ext/cpp_polygon_finder") do
|
|
6
|
+
Dir.glob("**/*.o").each { |f| File.delete(f) }
|
|
7
|
+
File.delete("Makefile") if File.exist?("Makefile")
|
|
6
8
|
system "ruby", "extconf.rb"
|
|
7
9
|
system "make", "-B"
|
|
10
|
+
Dir.glob("**/*.o").each { |f| File.delete(f) }
|
|
8
11
|
system "cp cpp_polygon_finder.so ./../../lib"
|
|
9
12
|
end
|
|
10
13
|
end
|
data/contrek.gemspec
CHANGED
|
@@ -4,8 +4,8 @@ require "contrek/version"
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "contrek"
|
|
6
6
|
s.version = Contrek::VERSION
|
|
7
|
-
s.summary = "
|
|
8
|
-
s.description = "Contrek is a Ruby
|
|
7
|
+
s.summary = "Fast PNG contour tracing and shape detection for Ruby"
|
|
8
|
+
s.description = "Contrek is a Ruby gem with a C++ core for fast contour tracing and edge detection in PNG images. It extracts polygonal contours from bitmap shapes, enabling image processing, shape analysis, and raster-to-vector workflows such as PNG to SVG conversion."
|
|
9
9
|
s.authors = ["Emanuele Cesaroni"]
|
|
10
10
|
s.email = "cesaroni.emanuele77@gmail.com"
|
|
11
11
|
s.homepage = "https://github.com/runout77/contrek"
|
|
@@ -14,7 +14,10 @@ Gem::Specification.new do |s|
|
|
|
14
14
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(pkg|spec)/}) }
|
|
15
15
|
end
|
|
16
16
|
s.metadata = {
|
|
17
|
-
"homepage_uri" => "https://github.com/runout77/contrek"
|
|
17
|
+
"homepage_uri" => "https://github.com/runout77/contrek",
|
|
18
|
+
"source_code_uri" => "https://github.com/runout77/contrek",
|
|
19
|
+
"documentation_uri" => "https://github.com/runout77/contrek#readme",
|
|
20
|
+
"changelog_uri" => "https://github.com/runout77/contrek/blob/main/CHANGELOG.md"
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
s.add_development_dependency "rspec", "~> 3.12"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
CXX = g++
|
|
2
|
+
CXXFLAGS = -Wall -Wextra -std=c++17 -Isrc -MMD -MP -pthread -g \
|
|
3
|
+
-fno-omit-frame-pointer -fsanitize=address,undefined \
|
|
4
|
+
-O0
|
|
5
|
+
LDFLAGS = -lpng -pthread -lz -fsanitize=address,undefined
|
|
6
|
+
|
|
7
|
+
# valgrinf or perf setup
|
|
8
|
+
#CXXFLAGS = -Wall -Wextra -std=c++17 -Isrc -MMD -MP -pthread -g -fno-omit-frame-pointer -march=native -DNDEBUG -O3
|
|
9
|
+
#LDFLAGS = -lpng -pthread -lz
|
|
10
|
+
|
|
11
|
+
# best performances
|
|
12
|
+
CXXFLAGS = -Wall -Wextra -std=c++17 -Isrc -MMD -MP -pthread -g -march=native -DNDEBUG -Ofast -flto
|
|
13
|
+
LDFLAGS = -lpng -pthread -lz -flto=auto
|
|
14
|
+
|
|
15
|
+
SRC = $(shell find src -name '*.cpp')
|
|
16
|
+
C_SRC = $(shell find src -name 'spng.c')
|
|
17
|
+
|
|
18
|
+
OBJ = $(patsubst src/%.cpp, build/%.o, $(SRC))
|
|
19
|
+
OBJ += $(patsubst src/%.c, build/%.o, $(C_SRC))
|
|
20
|
+
|
|
21
|
+
DEPS = $(OBJ:.o=.d)
|
|
22
|
+
|
|
23
|
+
EXEC = contrek
|
|
24
|
+
|
|
25
|
+
all: $(EXEC)
|
|
26
|
+
|
|
27
|
+
$(EXEC): $(OBJ)
|
|
28
|
+
$(CXX) $(CXXFLAGS) $(OBJ) -o $@ -Wl,--no-as-needed $(LDFLAGS)
|
|
29
|
+
|
|
30
|
+
build/%.o: src/%.cpp
|
|
31
|
+
@mkdir -p $(dir $@)
|
|
32
|
+
$(CXX) $(CXXFLAGS) -c $< -o $@
|
|
33
|
+
|
|
34
|
+
build/%.o: src/%.c
|
|
35
|
+
@mkdir -p $(dir $@)
|
|
36
|
+
$(CC) -O3 -march=native -fPIC -c $< -o $@
|
|
37
|
+
|
|
38
|
+
-include $(DEPS)
|
|
39
|
+
|
|
40
|
+
clean:
|
|
41
|
+
rm -rf build $(EXEC)
|
|
42
|
+
|
|
43
|
+
run: all
|
|
44
|
+
./$(EXEC)
|
|
Binary file
|
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
// Description :
|
|
7
7
|
//============================================================================
|
|
8
8
|
|
|
9
|
-
#include <iostream>
|
|
10
9
|
#include <string.h>
|
|
10
|
+
#include <iostream>
|
|
11
11
|
#include <list>
|
|
12
12
|
#include <map>
|
|
13
13
|
#include <string>
|
|
14
14
|
#include "polygon/finder/PolygonFinder.h"
|
|
15
15
|
#include "polygon/bitmaps/Bitmap.h"
|
|
16
|
-
#include "polygon/bitmaps/PngBitmap.h"
|
|
17
16
|
#include "polygon/bitmaps/FastPngBitmap.h"
|
|
18
17
|
#include "polygon/bitmaps/RemoteFastPngBitmap.h"
|
|
19
18
|
#include "polygon/matchers/Matcher.h"
|
|
@@ -22,12 +21,21 @@
|
|
|
22
21
|
#include "polygon/matchers/ValueNotMatcher.h"
|
|
23
22
|
#include "polygon/finder/optionparser.h"
|
|
24
23
|
#include "Tests.h"
|
|
25
|
-
|
|
24
|
+
#include "polygon/CpuTimer.h"
|
|
26
25
|
|
|
27
26
|
int main() {
|
|
28
|
-
|
|
29
|
-
test_suite
|
|
27
|
+
CpuTimer cpu_timer;
|
|
28
|
+
Tests test_suite;
|
|
29
|
+
cpu_timer.start();
|
|
30
|
+
|
|
31
|
+
// test_suite.test_a();
|
|
32
|
+
// test_suite.test_b();
|
|
33
|
+
// test_suite.test_c();
|
|
34
|
+
// test_suite.test_d();
|
|
35
|
+
test_suite.test_e();
|
|
36
|
+
std::cout << "compute time =" << cpu_timer.stop() << std::endl;
|
|
30
37
|
|
|
38
|
+
/*
|
|
31
39
|
FastPngBitmap png_bitmap("images/labyrinth.png");
|
|
32
40
|
std::cout << "image_w=" << png_bitmap.w() << " image_h=" << png_bitmap.h() << std::endl;
|
|
33
41
|
std::cout << "load_error=" << png_bitmap.error() << std::endl;
|
|
@@ -37,5 +45,6 @@ int main() {
|
|
|
37
45
|
RemoteFastPngBitmap bitmap(&data_url);
|
|
38
46
|
std::cout << "image_w=" << bitmap.w() << " image_h=" << bitmap.h() << std::endl;
|
|
39
47
|
std::cout << "load_error=" << bitmap.error() << std::endl;
|
|
48
|
+
*/
|
|
40
49
|
return 0;
|
|
41
50
|
}
|
|
@@ -7,15 +7,15 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
#include "Tests.h"
|
|
10
|
-
#include <iostream>
|
|
11
10
|
#include <string.h>
|
|
11
|
+
#include <iostream>
|
|
12
12
|
#include <list>
|
|
13
13
|
#include <map>
|
|
14
14
|
#include <vector>
|
|
15
15
|
#include <string>
|
|
16
16
|
#include "polygon/finder/PolygonFinder.h"
|
|
17
|
+
#include "polygon/finder/concurrent/ClippedPolygonFinder.h"
|
|
17
18
|
#include "polygon/bitmaps/Bitmap.h"
|
|
18
|
-
#include "polygon/bitmaps/PngBitmap.h"
|
|
19
19
|
#include "polygon/bitmaps/FastPngBitmap.h"
|
|
20
20
|
#include "polygon/bitmaps/RemoteFastPngBitmap.h"
|
|
21
21
|
#include "polygon/matchers/Matcher.h"
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
#include "polygon/matchers/RGBNotMatcher.h"
|
|
24
24
|
#include "polygon/matchers/ValueNotMatcher.h"
|
|
25
25
|
#include "polygon/finder/optionparser.h"
|
|
26
|
-
|
|
26
|
+
#include "polygon/finder/concurrent/Finder.h"
|
|
27
|
+
#include "polygon/finder/concurrent/Sequence.h"
|
|
28
|
+
#include "polygon/finder/concurrent/Position.h"
|
|
29
|
+
#include "polygon/finder/Polygon.h"
|
|
27
30
|
|
|
28
31
|
Tests::Tests() {
|
|
29
32
|
}
|
|
@@ -32,7 +35,7 @@ Tests::~Tests() {
|
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
void Tests::test_a()
|
|
35
|
-
{ string chunk =
|
|
38
|
+
{ std::string chunk =
|
|
36
39
|
"0000000000000000"\
|
|
37
40
|
"00000000000B0000"\
|
|
38
41
|
"000000AAAAAA0000"\
|
|
@@ -49,21 +52,139 @@ void Tests::test_a()
|
|
|
49
52
|
std::vector<int> outer_array{11, 1, 6, 2, 6, 3, 6, 4, 6, 5, 11, 5, 11, 4, 11, 3, 11, 2, 11, 1};
|
|
50
53
|
std::vector<int> inner_array{7, 3, 10, 3, 10, 4, 7, 4};
|
|
51
54
|
std::vector<int> array_compare;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
|
|
56
|
+
for (const auto& x : o->polygons)
|
|
57
|
+
{ for (const Point* p : x.outer) {
|
|
58
|
+
array_compare.push_back(p->x);
|
|
59
|
+
array_compare.push_back(p->y);
|
|
56
60
|
}
|
|
57
|
-
if (outer_array != array_compare) throw;
|
|
61
|
+
if (outer_array != array_compare) throw std::runtime_error("Wrong OUTER results!");
|
|
58
62
|
array_compare.clear();
|
|
59
|
-
|
|
60
|
-
for (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
array_compare.push_back((*y)->y);
|
|
63
|
+
for (const auto& z : x.inner)
|
|
64
|
+
{ for (const Point* y : z)
|
|
65
|
+
{ array_compare.push_back(y->x);
|
|
66
|
+
array_compare.push_back(y->y);
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
|
-
if (inner_array != array_compare) throw;
|
|
69
|
+
if (inner_array != array_compare) throw std::runtime_error("Wrong INNER results!");
|
|
67
70
|
}
|
|
71
|
+
delete o;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
void Tests::test_b()
|
|
75
|
+
{ std::string chunk =
|
|
76
|
+
// "0123456789012345"
|
|
77
|
+
" XXXXXXXXXXX "\
|
|
78
|
+
" XX XX "\
|
|
79
|
+
" XX XX "\
|
|
80
|
+
" XX XX "\
|
|
81
|
+
" XXXXXXXXXXX ";
|
|
82
|
+
// "0123456789012345"
|
|
83
|
+
/*chunk = " XXXXXXXXXXX "\
|
|
84
|
+
" XX XX XX "\
|
|
85
|
+
" XX XX XX "\
|
|
86
|
+
" XX XX XX "\
|
|
87
|
+
" XXXXXXXXXXX ";*/
|
|
88
|
+
|
|
89
|
+
std::vector<std::string> arguments = {"--versus=o", "--number_of_tiles=2", "--compress_uniq", "--compress_linear", "--treemap"};
|
|
90
|
+
ValueNotMatcher matcher(' ');
|
|
91
|
+
Bitmap b(chunk, 16);
|
|
92
|
+
|
|
93
|
+
/*ClippedPolygonFinder pl(&b, &matcher, 0, 16, &arguments);
|
|
94
|
+
ProcessResult *pr = pl.process_info();
|
|
95
|
+
pr->print_polygons();
|
|
96
|
+
delete pr;*/
|
|
97
|
+
|
|
98
|
+
Finder concurrentFinder(2, &b, &matcher, &arguments);
|
|
99
|
+
ProcessResult *pro = concurrentFinder.process_info();
|
|
100
|
+
pro->print_polygons();
|
|
101
|
+
delete pro;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
void Tests::test_c()
|
|
105
|
+
{ Sequence sequence;
|
|
106
|
+
Point* p1 = new Point({1, 1});
|
|
107
|
+
Point* p2 = new Point({2, 2});
|
|
108
|
+
Point* p3 = new Point({3, 3});
|
|
109
|
+
|
|
110
|
+
Hub* hub = new Hub(4, 4); // coordinate 0 -> 3 sia x che y, matrice di 4 * 4
|
|
111
|
+
|
|
112
|
+
Position* pos1 = new Position(hub, p1);
|
|
113
|
+
Position* pos2 = new Position(hub, p2);
|
|
114
|
+
Position* pos3 = new Position(hub, p3);
|
|
115
|
+
|
|
116
|
+
if (sequence.size != 0) throw std::runtime_error("Wrong initial sequence size");
|
|
117
|
+
sequence.add(pos1);
|
|
118
|
+
sequence.add(pos2);
|
|
119
|
+
sequence.add(pos3);
|
|
120
|
+
if (sequence.size != 3) throw std::runtime_error("Wrong sequence size");
|
|
121
|
+
|
|
122
|
+
// iterator() initially gives head
|
|
123
|
+
Point* head = sequence.head->payload;
|
|
124
|
+
if (head != p1) throw std::runtime_error("Wrong head");
|
|
125
|
+
if (sequence.iterator()->payload != p1) throw std::runtime_error("Wrong iterator to head");
|
|
126
|
+
if (sequence.iterator() != pos1) throw std::runtime_error("Wrong iterator to head");
|
|
127
|
+
// forward!
|
|
128
|
+
sequence.forward();
|
|
129
|
+
if (sequence.iterator()->payload != p2) throw std::runtime_error("Wrong iterator to position 2");
|
|
130
|
+
if (sequence.iterator() != pos2) throw std::runtime_error("Wrong iterator to position 2");
|
|
131
|
+
// forward!
|
|
132
|
+
sequence.forward();
|
|
133
|
+
if (sequence.iterator()->payload != p3) throw std::runtime_error("Wrong iterator to position 3");
|
|
134
|
+
if (sequence.iterator() != pos3) throw std::runtime_error("Wrong iterator to position 3");
|
|
135
|
+
|
|
136
|
+
// forward returns nullptr when no more items are present
|
|
137
|
+
sequence.forward();
|
|
138
|
+
if (sequence.iterator() != nullptr) throw std::runtime_error("Wrong iterator to end of list");
|
|
139
|
+
|
|
140
|
+
// rewind
|
|
141
|
+
sequence.rewind();
|
|
142
|
+
if (sequence.iterator()->payload != p1) throw std::runtime_error("Wrong iterator to head");
|
|
143
|
+
|
|
144
|
+
delete p1;
|
|
145
|
+
delete p2;
|
|
146
|
+
delete p3;
|
|
147
|
+
|
|
148
|
+
delete hub;
|
|
149
|
+
|
|
150
|
+
delete pos1;
|
|
151
|
+
delete pos2;
|
|
152
|
+
delete pos3;
|
|
68
153
|
}
|
|
69
154
|
|
|
155
|
+
void Tests::test_d()
|
|
156
|
+
{ CpuTimer cpu_timer;
|
|
157
|
+
cpu_timer.start();
|
|
158
|
+
FastPngBitmap png_bitmap("images/sample_10240x10240.png");
|
|
159
|
+
// FastPngBitmap png_bitmap("images/labyrinth.png");
|
|
160
|
+
std::cout << "image_w=" << png_bitmap.w() << " image_h=" << png_bitmap.h() << std::endl;
|
|
161
|
+
std::cout << "immagine =" << cpu_timer.stop() << std::endl;
|
|
162
|
+
|
|
163
|
+
int color = png_bitmap.value_at(0, 0);
|
|
164
|
+
std::cout << "color =" << color << std::endl;
|
|
165
|
+
RGBNotMatcher not_matcher(color);
|
|
166
|
+
|
|
167
|
+
std::vector<std::string> arguments = {"--versus=a", "--compress_uniq"};
|
|
168
|
+
PolygonFinder pl(&png_bitmap, ¬_matcher, nullptr, &arguments);
|
|
169
|
+
ProcessResult *o = pl.process_info();
|
|
170
|
+
o->print_info();
|
|
171
|
+
delete o;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
void Tests::test_e()
|
|
175
|
+
{ CpuTimer cpu_timer;
|
|
176
|
+
cpu_timer.start();
|
|
177
|
+
FastPngBitmap png_bitmap("images/sample_10240x10240.png");
|
|
178
|
+
// FastPngBitmap png_bitmap("images/sample_1024x1024.png");
|
|
179
|
+
std::cout << "immagine =" << cpu_timer.stop() << std::endl;
|
|
180
|
+
|
|
181
|
+
int color = png_bitmap.value_at(0, 0);
|
|
182
|
+
std::cout << "color =" << color << std::endl;
|
|
183
|
+
RGBNotMatcher not_matcher(color);
|
|
184
|
+
|
|
185
|
+
std::vector<std::string> arguments = {"--versus=a", "--compress_uniq", "--number_of_tiles=2"};
|
|
186
|
+
Finder pl(2, &png_bitmap, ¬_matcher, &arguments);
|
|
187
|
+
ProcessResult *o = pl.process_info();
|
|
188
|
+
o->print_info();
|
|
189
|
+
delete o;
|
|
190
|
+
}
|
|
@@ -6,14 +6,15 @@
|
|
|
6
6
|
* Copyright 2025 Emanuele Cesaroni
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
#define TESTS_H_
|
|
9
|
+
#pragma once
|
|
11
10
|
|
|
12
11
|
class Tests {
|
|
13
12
|
public:
|
|
14
13
|
Tests();
|
|
15
14
|
virtual ~Tests();
|
|
16
15
|
virtual void test_a();
|
|
16
|
+
virtual void test_b();
|
|
17
|
+
virtual void test_c();
|
|
18
|
+
virtual void test_d();
|
|
19
|
+
virtual void test_e();
|
|
17
20
|
};
|
|
18
|
-
|
|
19
|
-
#endif /* TESTS_H_ */
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* CpuTimer.h
|
|
3
|
+
*
|
|
4
|
+
* Created on: 23 nov 2025
|
|
5
|
+
* Author: ema
|
|
6
|
+
* Copyright 2025 Emanuele Cesaroni
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#pragma once
|
|
10
|
+
#include <chrono>
|
|
11
|
+
|
|
12
|
+
class CpuTimer {
|
|
13
|
+
using Clock = std::chrono::steady_clock;
|
|
14
|
+
std::chrono::time_point<Clock> _start;
|
|
15
|
+
bool _running = false;
|
|
16
|
+
|
|
17
|
+
public:
|
|
18
|
+
void start() {
|
|
19
|
+
_start = Clock::now();
|
|
20
|
+
_running = true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
double stop() {
|
|
24
|
+
if (!_running) return 0.0;
|
|
25
|
+
auto duration = elapsed_ms();
|
|
26
|
+
_running = false;
|
|
27
|
+
return duration;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
double elapsed_ms() const {
|
|
31
|
+
if (!_running) return 0.0;
|
|
32
|
+
auto end = Clock::now();
|
|
33
|
+
std::chrono::duration<double, std::milli> diff = end - _start;
|
|
34
|
+
return diff.count();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
void reset() {
|
|
38
|
+
_running = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
bool is_running() const {
|
|
42
|
+
return _running;
|
|
43
|
+
}
|
|
44
|
+
};
|