contrek 1.4.0 → 1.4.2
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 -1
- data/Gemfile.lock +4 -1
- data/PERFORMANCE.md +40 -4
- data/README.md +211 -59
- data/contrek.gemspec +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h +37 -11
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +53 -144
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.cpp +14 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.h +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/AsciiSource.cpp +69 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/AsciiSource.h +34 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/PngSource.cpp +108 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/PngSource.h +40 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/RasterSource.h +22 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/RasterStreamer.h +92 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +6 -8
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +8 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +4 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +7 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Poolable.h +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +3 -3
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +47 -0
- data/lib/contrek/bitmaps/bitmap.rb +2 -2
- data/lib/contrek/bitmaps/chunky_bitmap.rb +23 -8
- data/lib/contrek/bitmaps/png_bitmap.rb +4 -0
- data/lib/contrek/bitmaps/rgba_bitmap.rb +50 -0
- data/lib/contrek/bitmaps/streaming/raster_streamer.rb +66 -0
- data/lib/contrek/bitmaps/streaming/sources/ascii.rb +33 -0
- data/lib/contrek/bitmaps/streaming/sources/base.rb +27 -0
- data/lib/contrek/bitmaps/streaming/sources/png.rb +105 -0
- data/lib/contrek/finder/concurrent/finder.rb +5 -3
- data/lib/contrek/finder/polygon_finder.rb +8 -4
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +5 -0
- metadata +26 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 330f21ed351ac9ec5b507c77c49c0595264d174f5403d00449cd6abf25be9e98
|
|
4
|
+
data.tar.gz: 33813776ce0b886f1807d08e6e3245fb4a2276cb6da7ae0fdc571886101160ae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d9ee2d8f093dad25a2c6913247a3c719fbf63d6c818ca3b451b27c8ec8984eeaa1760688e4bb5baf799afd3ea6b712c1db3940eb46fea859a6b3336815ad5e1f
|
|
7
|
+
data.tar.gz: b542aec71f43fa199e300d745838ac5ff25e3c3b34e988ea6e1ca58051ac9f93723d2caa5d0ba2a4102fd0c30daf7b1ad3ced65f4ee3d077d1fff85a824c2960
|
data/CHANGELOG.md
CHANGED
|
@@ -149,7 +149,13 @@ All notable changes to this project will be documented in this file.
|
|
|
149
149
|
- Fixed issue found on treemap determination.
|
|
150
150
|
|
|
151
151
|
## [1.3.9] - 2026-08-09
|
|
152
|
-
- Removed the curses development dependency along with the old TerminalTracker; actually Bitmaps::
|
|
152
|
+
- Removed the curses development dependency along with the old TerminalTracker; actually Contrek::Bitmaps::PngBitmap.to_window renders visually better results.
|
|
153
153
|
|
|
154
154
|
## [1.4.0] - 2026-08-13
|
|
155
155
|
- Added deterministic merge mode usefull when you need a byte-for-byte reproducible outputs across different runs (by default, the merge stage is non-deterministic to maximize performance and thread utilization).
|
|
156
|
+
|
|
157
|
+
## [1.4.1] - 2026-08-23
|
|
158
|
+
- The number_of_threads effective value is now returned inside metadata output struct too.
|
|
159
|
+
|
|
160
|
+
## [1.4.2] - 2026-09-06
|
|
161
|
+
- Contrek takes care of the streaming process, providing an API that greatly simplifies the implementation of streaming raster processing.
|
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
contrek (1.4.
|
|
4
|
+
contrek (1.4.2)
|
|
5
5
|
chunky_png (~> 1.4)
|
|
6
6
|
concurrent-ruby (~> 1.3.5)
|
|
7
|
+
ffi (~> 1.17)
|
|
7
8
|
rice (= 4.5.0)
|
|
8
9
|
|
|
9
10
|
GEM
|
|
@@ -14,6 +15,8 @@ GEM
|
|
|
14
15
|
chunky_png (1.4.0)
|
|
15
16
|
concurrent-ruby (1.3.5)
|
|
16
17
|
diff-lcs (1.6.1)
|
|
18
|
+
ffi (1.17.4)
|
|
19
|
+
ffi (1.17.4-x86_64-linux-gnu)
|
|
17
20
|
json (2.11.3)
|
|
18
21
|
language_server-protocol (3.17.0.4)
|
|
19
22
|
lint_roller (1.1.0)
|
data/PERFORMANCE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Contrek Performance Tuning
|
|
2
2
|
|
|
3
3
|
This document describes optional dependencies and configuration tips to get the best performance out of Contrek on large images.
|
|
4
4
|
|
|
@@ -41,7 +41,7 @@ sudo make install
|
|
|
41
41
|
sudo ldconfig
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
>
|
|
44
|
+
> The `-DZLIB_COMPAT=ON` flag is mandatory. Without it, zlib-ng uses a different ABI and CMake's `find_package(ZLIB)` won't detect it.
|
|
45
45
|
|
|
46
46
|
**macOS:**
|
|
47
47
|
```bash
|
|
@@ -136,9 +136,45 @@ Consider this depends your system. Probably is better not to saturate all cores
|
|
|
136
136
|
|
|
137
137
|
---
|
|
138
138
|
|
|
139
|
-
## 4.
|
|
139
|
+
## 4. Transparent Huge Pages (THP) — Kernel Memory Allocation
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
When processing high-resolution images, Contrek performs dense dynamic memory allocations. By default, Linux manages memory in standard 4 KiB pages, which creates heavy kernel overhead from frequent page faults and Translation Lookaside Buffer (TLB) misses under multithreaded loads.
|
|
142
|
+
|
|
143
|
+
Enabling **Transparent Huge Pages (`always`)** forces the Linux kernel to allocate memory in **2 MiB blocks**, reducing page table lookup entries by a factor of 512 and dramatically speeding up memory access.
|
|
144
|
+
|
|
145
|
+
### Temporary Activation
|
|
146
|
+
|
|
147
|
+
To enable it immediately for your current Linux session:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
echo always | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Permanent Activation
|
|
154
|
+
To ensure this setting persists across system reboots:
|
|
155
|
+
|
|
156
|
+
Open /etc/default/grub in an editor:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
sudo nano /etc/default/grub
|
|
160
|
+
```
|
|
161
|
+
Add transparent_hugepage=always to GRUB_CMDLINE_LINUX_DEFAULT:
|
|
162
|
+
|
|
163
|
+
```Plaintext
|
|
164
|
+
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash transparent_hugepage=always"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Update GRUB:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
sudo update-grub
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 5. Combining All Optimizations
|
|
176
|
+
|
|
177
|
+
Install zlib-ng and tcmalloc, enable THP, then configure:
|
|
142
178
|
|
|
143
179
|
```ruby
|
|
144
180
|
# Ruby
|
data/README.md
CHANGED
|
@@ -8,6 +8,8 @@ The engine is based on programmable pixel matchers, allowing the caller to decid
|
|
|
8
8
|
|
|
9
9
|
Although the tracing engine is written in C++, Contrek is also distributed as a Ruby gem exposing almost the complete native API through an idiomatic Ruby interface.
|
|
10
10
|
|
|
11
|
+
A **Python** binding is available 👉 **[here](https://github.com/runout77/contrek-python)**
|
|
12
|
+
|
|
11
13
|
## About Contrek
|
|
12
14
|
|
|
13
15
|
**Contrek** (**CON**tour **TREK**king) at its simplest, the library scans a bitmap and returns the contours of all regions matching a user-defined criterion. Each region is represented by one outer polygon and, when necessary, one or more inner polygons describing holes.
|
|
@@ -25,58 +27,227 @@ The image below shows a simple example. Every non-white pixel is considered part
|
|
|
25
27
|
|
|
26
28
|
Most contour tracing libraries process an image sequentially. That approach is perfectly adequate for many workloads, but it becomes less practical once images become very large or when memory usage starts to matter.
|
|
27
29
|
|
|
28
|
-
Contrek
|
|
30
|
+
Contrek supports monothread extraction but also different strategies.
|
|
31
|
+
|
|
32
|
+
### Mode 1: Single-threaded processing
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
<table>
|
|
35
|
+
<tr>
|
|
36
|
+
<td width="40%" valign="top">
|
|
37
|
+
<img src="docs/images/modes/mode1.jpg" width="100%" alt="Mode 1">
|
|
38
|
+
</td>
|
|
39
|
+
<td width="60%" valign="top">
|
|
40
|
+
The entire image is processed using a single core.
|
|
31
41
|
|
|
32
|
-
|
|
42
|
+
**Profile:** low speed; low memory efficiency.
|
|
43
|
+
<br><br>
|
|
44
|
+
<center><img src="docs/images/modes/mode1_panel.jpg" width="80%" alt="Mode 1"></center>
|
|
45
|
+
</td></tr></table>
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
Trace polygons from a PNG file (`CPPPngBitMap`).
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
png_bitmap = CPPPngBitMap.new("spec/files/images/labyrinth3.png")
|
|
51
|
+
color = Contrek::Bitmaps::RgbCppColor.new(r: 255, g: 255, b: 255, a: 255)
|
|
52
|
+
rgb_matcher = CPPRGBNotMatcher.new(color.raw)
|
|
53
|
+
polygonfinder = CPPPolygonFinder.new(
|
|
54
|
+
png_bitmap,
|
|
55
|
+
rgb_matcher,
|
|
56
|
+
nil,
|
|
57
|
+
{ versus: :a,
|
|
58
|
+
compress: {
|
|
59
|
+
visvalingam: true,
|
|
60
|
+
visvalingam_tolerance: 1.5
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
result = polygonfinder.process_info
|
|
65
|
+
puts result.metadata[:number_of_threads] # => 0
|
|
66
|
+
```
|
|
36
67
|
|
|
37
|
-
|
|
68
|
+
`versus` accepts `:a`,`:o` for "anticlockwise"/"clockwise".
|
|
38
69
|
|
|
39
|
-
## Parallel execution
|
|
40
70
|
|
|
41
|
-
|
|
71
|
+
### Mode 2: Parallel processing
|
|
42
72
|
|
|
43
|
-
|
|
73
|
+
<table>
|
|
74
|
+
<tr>
|
|
75
|
+
<td width="40%" valign="top">
|
|
76
|
+
<img src="docs/images/modes/mode2.jpg" width="100%" alt="Mode 1">
|
|
77
|
+
</td>
|
|
78
|
+
<td width="60%" valign="top">
|
|
79
|
+
The entire image is loaded first, then split into tiles and processed across multiple CPU cores. Partial results are progressively and dynamically merged: there is no predefined merge order, and adjacent pairs are processed as soon as they become available.
|
|
80
|
+
This mode prioritizes performance, using parallelism both for tile processing and for merging partial results.
|
|
44
81
|
|
|
45
|
-
|
|
82
|
+
**Profile:** maximum speed, with processing time decreasing as more cores become available; low memory efficiency.
|
|
83
|
+
<br><br>
|
|
84
|
+
<center><img src="docs/images/modes/mode2_panel.jpg" width="80%" alt="Mode 1"></center>
|
|
85
|
+
</td></tr></table>
|
|
46
86
|
|
|
47
|
-
|
|
87
|
+
Use 8 threads and 8 tiles
|
|
48
88
|
|
|
49
|
-
|
|
89
|
+
```ruby
|
|
90
|
+
png_bitmap = CPPPngBitMap.new("spec/files/images/sample_10240x10240.png")
|
|
91
|
+
color = Contrek::Bitmaps::RgbCppColor.new(r: 255, g: 255, b: 255, a: 255)
|
|
92
|
+
rgb_matcher = CPPRGBNotMatcher.new(color.raw)
|
|
93
|
+
polygonfinder = Contrek::Cpp::CPPConcurrentFinder.new(
|
|
94
|
+
number_of_threads: 8,
|
|
95
|
+
bitmap: png_bitmap,
|
|
96
|
+
matcher: rgb_matcher,
|
|
97
|
+
options: {number_of_tiles: 8, versus: :o, compress: {uniq: true}}
|
|
98
|
+
)
|
|
99
|
+
result = polygonfinder.process_info
|
|
100
|
+
puts result.metadata[:benchmarks].inspect
|
|
101
|
+
# => {"compress"=>5.08, "init"=>230.79, "inner"=>6.54, "outer"=>84.11, "total"=>235.87}
|
|
102
|
+
puts result.metadata[:number_of_threads] # => 8
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Mode 3: Input streaming
|
|
50
106
|
|
|
51
|
-
|
|
107
|
+
<table>
|
|
108
|
+
<tr>
|
|
109
|
+
<td width="40%" valign="top">
|
|
110
|
+
<img src="docs/images/modes/mode3.jpg" width="100%" alt="Mode 1">
|
|
111
|
+
</td>
|
|
112
|
+
<td width="60%" valign="top">
|
|
113
|
+
The image does not need to be loaded entirely into memory. Instead, it can be read progressively using a fixed-size buffer. For example, with a PNG source, this can be done using libspng's progressive decoding.
|
|
114
|
+
Adjacent tiles share an overlapping scanline to preserve geometry continuity across tile boundaries. Once all tiles have been added, the merge is performed (optionally using multiple threads) to reconstruct the complete geometry.
|
|
115
|
+
This mode provides a trade-off between performance and memory usage: the entire raster does not need to be kept in RAM, while the vector state required to build the final result is retained.
|
|
116
|
+
|
|
117
|
+
**Profile:** medium speed; medium-high memory efficiency.
|
|
118
|
+
<br><br>
|
|
119
|
+
<center><img src="docs/images/modes/mode3_panel.jpg" width="80%" alt="Mode 1"></center>
|
|
120
|
+
</td></tr></table>
|
|
121
|
+
|
|
122
|
+
Trace polygons from two in-memory pattern strings (`CPPBitMap`, useful for synthetic tiles or tests).
|
|
123
|
+
Up is 6 rows height, down is 5 rows. Total after merging: 10 rows, because one row is the shared scanline
|
|
52
124
|
|
|
53
|
-
|
|
125
|
+
```ruby
|
|
126
|
+
up = " 00000000000000 " \
|
|
127
|
+
" 00000000000000 " \
|
|
128
|
+
" 00 00 " \
|
|
129
|
+
" 00 00 " \
|
|
130
|
+
" 00 00 " \
|
|
131
|
+
" 00 00 "
|
|
132
|
+
|
|
133
|
+
down = " 00 00 " \
|
|
134
|
+
" 00 00 " \
|
|
135
|
+
" 00 00 " \
|
|
136
|
+
" 00000000000000 " \
|
|
137
|
+
" 00000000000000 "
|
|
138
|
+
matcher = CPPValueNotMatcher.new(" ")
|
|
139
|
+
result_up = CPPPolygonFinder.new(CPPBitMap.new(up, 30),
|
|
140
|
+
matcher,
|
|
141
|
+
nil,
|
|
142
|
+
{versus: :a, bounds: true}).process_info
|
|
143
|
+
result_down = CPPPolygonFinder.new(CPPBitMap.new(down, 30),
|
|
144
|
+
matcher,
|
|
145
|
+
nil,
|
|
146
|
+
{versus: :a, bounds: true}).process_info
|
|
147
|
+
|
|
148
|
+
step_finder = Contrek::Cpp::CPPConcurrentVerticalMerger.new(options: {compress: {linear: true}})
|
|
149
|
+
step_finder.add_tile(result_up)
|
|
150
|
+
step_finder.add_tile(result_down)
|
|
151
|
+
result = step_finder.process_info
|
|
152
|
+
|
|
153
|
+
expect(result.metadata[:groups]).to eq(1)
|
|
154
|
+
expect(result.metadata[:width]).to eq(30)
|
|
155
|
+
expect(result.metadata[:height]).to eq(10)
|
|
156
|
+
```
|
|
157
|
+
See an other example of **[input streaming graphical result](docs/images/stripes/merging_polygons.md)**.
|
|
158
|
+
|
|
159
|
+
The same operation using Contrek's built-in streaming API and progressive PNG decoding via libspng:
|
|
54
160
|
|
|
55
|
-
|
|
161
|
+
```ruby
|
|
162
|
+
# PNG image to stream from
|
|
163
|
+
source = CPPPngSource.new("./spec/files/images/labyrinth2.png")
|
|
164
|
+
# streamer with a 20-row window
|
|
165
|
+
streamer = CPPRasterStreamer.new(source, stripe_height: 20)
|
|
166
|
+
white = Contrek::Bitmaps::RgbCppColor.new(r: 255, g: 255, b: 255, a: 255)
|
|
167
|
+
matcher = CPPRGBNotMatcher.new(white.raw)
|
|
168
|
+
# reusable bitmap where libspng decodes rows directly
|
|
169
|
+
buffer_bitmap = CPPRawBitMap.new(source.width, streamer.stripe_height)
|
|
170
|
+
finder = Contrek::Cpp::CPPConcurrentVerticalMerger.new(options: {compress: {uniq: true, linear: true}})
|
|
171
|
+
|
|
172
|
+
# streaming loop, 20 rows each
|
|
173
|
+
streamer.each(buffer_bitmap) do |bitmap, buffer_rows, buffer_size|
|
|
174
|
+
tile = CPPPolygonFinder.new(
|
|
175
|
+
bitmap,
|
|
176
|
+
matcher,
|
|
177
|
+
nil,
|
|
178
|
+
{processing_height: buffer_rows, versus: :o, bounds: true}
|
|
179
|
+
).process_info
|
|
180
|
+
finder.add_tile(tile)
|
|
181
|
+
end
|
|
182
|
+
result = finder.process_info
|
|
183
|
+
puts result.metadata[:width] # => 130
|
|
184
|
+
puts result.metadata[:height] # => 130
|
|
185
|
+
puts result.points[0][:outer].first.inspect # => {:x=>10, :y=>4} # first point
|
|
186
|
+
puts result.points[0][:outer].size # => 1171 points
|
|
187
|
+
```
|
|
56
188
|
|
|
57
|
-
|
|
189
|
+
### Mode 4: End-to-end streaming
|
|
58
190
|
|
|
59
191
|
<table>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
192
|
+
<tr>
|
|
193
|
+
<td width="40%" valign="top">
|
|
194
|
+
<img src="docs/images/modes/mode4.jpg" width="100%" alt="Mode 1">
|
|
195
|
+
</td>
|
|
196
|
+
<td width="60%" valign="top">
|
|
197
|
+
This mode extends the incremental processing used in Mode 3 by streaming the output as well.
|
|
198
|
+
The image is read and processed one tile at a time. Each new tile is immediately merged with the current state. As processing moves forward, whenever a geometry is complete and can no longer be affected by subsequent tiles, it is finalized and written directly to the SVG file.
|
|
199
|
+
This limits both the amount of raster data kept in memory and the accumulation of generated vector geometries. It is particularly well suited to very large datasets or cases where the vector output itself can become significant in size.
|
|
200
|
+
|
|
201
|
+
**Profile:** low speed; maximum memory efficiency.
|
|
202
|
+
<br><br>
|
|
203
|
+
<center><img src="docs/images/modes/mode4_panel.jpg" width="80%" alt="Mode 1"></center>
|
|
204
|
+
</td></tr></table>
|
|
205
|
+
|
|
206
|
+
Trace polygons from 2 in-memory pattern strings.
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
stripe1 = " 000 000" \
|
|
210
|
+
" 00 00 00 0" \
|
|
211
|
+
" 00 00 00 0" \
|
|
212
|
+
" 00 00 00 0" \
|
|
213
|
+
" 000 00 0" \
|
|
214
|
+
" 00 0"
|
|
215
|
+
|
|
216
|
+
stripe2 = " 00 0" \
|
|
217
|
+
" 000 00 0" \
|
|
218
|
+
"00 00 00 0" \
|
|
219
|
+
" 000 00 0" \
|
|
220
|
+
" 00 0" \
|
|
221
|
+
" 0000000000000"
|
|
222
|
+
# streaming to svg file pattern
|
|
223
|
+
stripes = [stripe1, stripe2]
|
|
224
|
+
shared_stream = Contrek::Cpp::CPPTempfile.new("output.svg")
|
|
225
|
+
step_finder = Contrek::Cpp::CPPSvgConcurrentStreamingMerger.new(
|
|
226
|
+
options: {bounds: true, compress: {uniq: true, linear: true, douglas_peucker: true}},
|
|
227
|
+
stream_to: shared_stream,
|
|
228
|
+
total_width: 18,
|
|
229
|
+
total_height: 11
|
|
230
|
+
)
|
|
231
|
+
matcher = CPPValueNotMatcher.new(" ")
|
|
232
|
+
stripes.each do |stripe|
|
|
233
|
+
stripe_result = CPPPolygonFinder.new(CPPBitMap.new(stripe, 18),
|
|
234
|
+
matcher,
|
|
235
|
+
nil,
|
|
236
|
+
{versus: :a, bounds: true}).process_info
|
|
237
|
+
last = stripes.last == stripe
|
|
238
|
+
step_finder.add_tile(stripe_result, last)
|
|
239
|
+
end
|
|
240
|
+
result = step_finder.process_info
|
|
241
|
+
expect(result.metadata[:groups]).to eq(3)
|
|
242
|
+
expect(result.metadata[:width]).to eq(18)
|
|
243
|
+
expect(result.metadata[:height]).to eq(11)
|
|
244
|
+
shared_stream.rewind
|
|
245
|
+
puts shared_stream.read.inspect
|
|
246
|
+
# => "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"11\">
|
|
247
|
+
# <polygon points=\"3,3 5,5 8,5 10,3 8,0 5,0\" class=\"out\"/>
|
|
248
|
+
# <polygon points=\"7,1 8,3 6,4 5,2\" class=\"in\"/>
|
|
249
|
+
# <polygon points=\"5,11 18,11 18,0 15,0\" class=\"out\"/> ...
|
|
250
|
+
```
|
|
80
251
|
|
|
81
252
|
## Benchmarking
|
|
82
253
|
|
|
@@ -166,7 +337,8 @@ Metadata includes timing information (expressed in milliseconds), the number of
|
|
|
166
337
|
},
|
|
167
338
|
:groups=>2,
|
|
168
339
|
:named_sequence=>"",
|
|
169
|
-
:treemap=>[]
|
|
340
|
+
:treemap=>[],
|
|
341
|
+
:number_of_threads=>0
|
|
170
342
|
}
|
|
171
343
|
```
|
|
172
344
|
|
|
@@ -600,7 +772,8 @@ Typical information includes
|
|
|
600
772
|
benchmarks:{...},
|
|
601
773
|
groups:1,
|
|
602
774
|
treemap:[],
|
|
603
|
-
options:{...}
|
|
775
|
+
options:{...},
|
|
776
|
+
number_of_threads: 4
|
|
604
777
|
}
|
|
605
778
|
```
|
|
606
779
|
|
|
@@ -717,27 +890,6 @@ Instead, it repeatedly combines smaller topologically consistent pieces into a l
|
|
|
717
890
|
This strategy makes it possible to process images whose full size would otherwise exceed the available memory.
|
|
718
891
|
|
|
719
892
|
|
|
720
|
-
## Merge order
|
|
721
|
-
|
|
722
|
-
The merge order is intentionally left to the thread scheduler.
|
|
723
|
-
|
|
724
|
-
For three stripes, both of the following execution orders are valid:
|
|
725
|
-
|
|
726
|
-
```
|
|
727
|
-
(B1 + B2) + B3
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
or
|
|
731
|
-
|
|
732
|
-
```
|
|
733
|
-
B1 + (B2 + B3)
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
Although the intermediate merge sequence changes, the reconstructed polygons remain geometrically equivalent.
|
|
737
|
-
|
|
738
|
-
The exact ordering of coordinates may differ because polygon simplification is applied after merging.
|
|
739
|
-
|
|
740
|
-
|
|
741
893
|
## Polygon reconstruction
|
|
742
894
|
|
|
743
895
|
Internally, polygons crossing a stripe boundary are temporarily divided into smaller pieces.
|
data/contrek.gemspec
CHANGED
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
|
|
22
22
|
namespace Contrek {
|
|
23
23
|
|
|
24
|
+
enum class Versus {
|
|
25
|
+
A, // Anticlockwise
|
|
26
|
+
O // Clockwise
|
|
27
|
+
};
|
|
28
|
+
|
|
24
29
|
enum class MatchMode {
|
|
25
30
|
NOT_COLOR, // Tracks border of what is not target color
|
|
26
31
|
EXACT_COLOR // Tracks border of what exactly matchs target color
|
|
@@ -31,15 +36,22 @@ enum class Connectivity {
|
|
|
31
36
|
OMNIDIRECTIONAL = 8 // 8 directions
|
|
32
37
|
};
|
|
33
38
|
|
|
39
|
+
|
|
34
40
|
struct Config {
|
|
35
|
-
int threads =
|
|
36
|
-
int tiles =
|
|
41
|
+
int threads = 0;
|
|
42
|
+
int tiles = 1;
|
|
43
|
+
Versus versus = Versus::A;
|
|
37
44
|
bool compress_unique = false;
|
|
38
45
|
bool compress_linear = false;
|
|
39
46
|
bool compress_raster = false;
|
|
40
47
|
bool compress_douglas_peucker = false;
|
|
41
48
|
bool compress_visvalingam = false;
|
|
49
|
+
float compress_visvalingam_tolerance = 0.0f;
|
|
42
50
|
bool treemap = false;
|
|
51
|
+
bool bounds = false;
|
|
52
|
+
bool named_sequences = false;
|
|
53
|
+
bool unsafe_mode = false;
|
|
54
|
+
bool deterministic = false;
|
|
43
55
|
int32_t target_color = -1;
|
|
44
56
|
MatchMode mode = MatchMode::NOT_COLOR;
|
|
45
57
|
Connectivity connectivity_mode = Connectivity::ORTHOGONAL;
|
|
@@ -81,19 +93,33 @@ inline TraceContext trace(const std::string& image_path, const Config& cfg = Con
|
|
|
81
93
|
ctx.matcher = std::make_unique<RGBMatcher>(color_to_match);
|
|
82
94
|
}
|
|
83
95
|
|
|
96
|
+
std::string versus_str = (cfg.versus == Versus::O) ? "o" : "a";
|
|
84
97
|
ctx.internal_args = {
|
|
85
|
-
{"versus", Identifier{
|
|
98
|
+
{"versus", Identifier{versus_str}},
|
|
99
|
+
{"number_of_tiles", static_cast<int64_t>(cfg.tiles)},
|
|
100
|
+
{"treemap", cfg.treemap},
|
|
101
|
+
{"bounds", cfg.bounds},
|
|
102
|
+
{"named_sequences", cfg.named_sequences},
|
|
103
|
+
{"unsafe_mode", cfg.unsafe_mode},
|
|
104
|
+
{"deterministic", cfg.deterministic}
|
|
86
105
|
};
|
|
87
|
-
Options compression_opts;
|
|
88
|
-
|
|
89
|
-
if (cfg.compress_unique) compression_opts["uniq"] = true;
|
|
90
|
-
if (cfg.compress_linear) compression_opts["linear"] = true;
|
|
91
|
-
if (cfg.compress_visvalingam) compression_opts["visvalingam"] = true;
|
|
92
|
-
if (cfg.compress_unique || cfg.compress_linear || cfg.compress_visvalingam) ctx.internal_args["compression"] = compression_opts;
|
|
93
|
-
ctx.internal_args["number_of_tiles"] = cfg.tiles;
|
|
94
106
|
if (cfg.connectivity_mode == Connectivity::OMNIDIRECTIONAL) {
|
|
95
|
-
ctx.internal_args["connectivity"] = 8;
|
|
107
|
+
ctx.internal_args["connectivity"] = static_cast<int64_t>(8);
|
|
96
108
|
}
|
|
109
|
+
Options compress_opts;
|
|
110
|
+
if (cfg.compress_unique) compress_opts["uniq"] = true;
|
|
111
|
+
if (cfg.compress_linear) compress_opts["linear"] = true;
|
|
112
|
+
if (cfg.compress_raster) compress_opts["raster"] = true;
|
|
113
|
+
if (cfg.compress_douglas_peucker) compress_opts["douglas_peucker"] = true;
|
|
114
|
+
if (cfg.compress_visvalingam) compress_opts["visvalingam"] = true;
|
|
115
|
+
if (cfg.compress_visvalingam_tolerance > 0.0f) {
|
|
116
|
+
compress_opts["visvalingam_tolerance"] = static_cast<double>(cfg.compress_visvalingam_tolerance);
|
|
117
|
+
}
|
|
118
|
+
if (cfg.compress_unique || cfg.compress_linear || cfg.compress_raster ||
|
|
119
|
+
cfg.compress_douglas_peucker || cfg.compress_visvalingam) {
|
|
120
|
+
ctx.internal_args["compress"] = compress_opts;
|
|
121
|
+
}
|
|
122
|
+
|
|
97
123
|
ctx.finder = std::make_unique<Finder>(cfg.threads, ctx.bitmap.get(), ctx.matcher.get(), ctx.internal_args);
|
|
98
124
|
ctx.result = std::unique_ptr<ProcessResult>(ctx.finder->process_info());
|
|
99
125
|
|