contrek 1.1.4 → 1.1.6
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 +10 -0
- data/Gemfile.lock +1 -1
- data/README.md +79 -2
- data/docs/images/strict_bounds_off.png +0 -0
- data/docs/images/strict_bounds_on.png +0 -0
- data/docs/images/stripes/whole.png +0 -0
- data/docs/images/stripes/whole_0.png +0 -0
- data/docs/images/stripes/whole_256.png +0 -0
- data/docs/images/stripes/whole_512.png +0 -0
- data/docs/images/stripes/whole_768.png +0 -0
- data/ext/cpp_polygon_finder/PolygonFinder/examples/example.cpp +4 -1
- data/ext/cpp_polygon_finder/PolygonFinder/images/graphs_1024x1024.png +0 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +184 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.h +3 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.cpp +12 -48
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.h +2 -11
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.cpp +163 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.h +41 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RemoteFastPngBitmap.cpp +2 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +7 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +16 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +2 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PointPool.cpp +19 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PointPool.h +24 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +32 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +51 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/RectBounds.h +12 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +10 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.h +8 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.cpp +22 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/HorizontalMerger.h +19 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.cpp +46 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.h +24 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queue.h +5 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.cpp +64 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.h +24 -0
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +109 -4
- data/lib/contrek/bitmaps/chunky_bitmap.rb +50 -2
- data/lib/contrek/bitmaps/{custom_bitmap.rb → raw_bitmap.rb} +1 -1
- data/lib/contrek/cpp/cpp_concurrent_horizontal_merger.rb +9 -0
- data/lib/contrek/cpp/cpp_concurrent_merger.rb +9 -0
- data/lib/contrek/cpp/cpp_concurrent_vertical_merger.rb +9 -0
- data/lib/contrek/cpp/cpp_result.rb +42 -0
- data/lib/contrek/finder/concurrent/finder.rb +8 -5
- data/lib/contrek/finder/concurrent/horizontal_merger.rb +10 -0
- data/lib/contrek/finder/concurrent/merger.rb +52 -0
- data/lib/contrek/finder/concurrent/part.rb +9 -3
- data/lib/contrek/finder/concurrent/tile.rb +0 -2
- data/lib/contrek/finder/concurrent/vertical_merger.rb +44 -0
- data/lib/contrek/finder/node_cluster.rb +15 -6
- data/lib/contrek/finder/polygon_finder.rb +3 -1
- data/lib/contrek/finder/result.rb +1 -5
- data/lib/contrek/reducers/linear_reducer.rb +1 -1
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +8 -2
- metadata +28 -4
- data/lib/contrek/results/cpp_result.rb +0 -21
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1646e5fe0beaeafefcf2d60230a12ead391c9d75c771807332de7e06128198e4
|
|
4
|
+
data.tar.gz: a42d904e10ca321d8091263af986193332ac1507cdb2155e92373cf222faa864
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 32495e09d3c742d1c8a1011df2c08a204a510766a0bc4fa5bf9f647fcba5c15fe691fc5ef7207f1caeb9c9caa0ff041948cc12f6e042261dd1b1d49e69c8f430
|
|
7
|
+
data.tar.gz: 28a0e9ff0cc67cfbe7f56f73ae660d68a1ca82243ac656a67cff4c9aefff506eaf0230edc7973de9d900cb1f63e4946f9eed8c39cd9a04de01861dbcf04cb6cf
|
data/CHANGELOG.md
CHANGED
|
@@ -58,3 +58,13 @@ All notable changes to this project will be documented in this file.
|
|
|
58
58
|
### Changed
|
|
59
59
|
- Fixed an infinite loop bug in multithreading during inner sequence joining in Omnidirectional mode.
|
|
60
60
|
- Optimized C++ and Ruby algorithms for initial spatial tangential sequence determination.
|
|
61
|
+
|
|
62
|
+
## [1.1.5] - 2026-03-08
|
|
63
|
+
### Changed
|
|
64
|
+
- **RawBitmap Integration (Ruby/C++):** Introduced a native buffer class for direct contour extraction, bypassing PNG encoding to significantly reduce latency and memory overhead.
|
|
65
|
+
- **Tiled Polygon Merger:** Added a spatial merger for `Finder` outputs to stitch polygons from discrete sub-areas (requiring only a 1px overlap). This multi-phase workflow supports coordinate translation and eliminates monolithic buffer requirements, optimizing the peak memory footprint.
|
|
66
|
+
|
|
67
|
+
## [1.1.6] - 2026-03-21
|
|
68
|
+
### Changed
|
|
69
|
+
- Added strict_bounds tracing mode: enables more accurate shape tracing by strictly adhering to pixel boundaries.
|
|
70
|
+
- Topological Consistency Fixes: improved the Topologically Consistent Merging algorithm to support progressive polygon tracing during sequential data streaming.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -1,14 +1,53 @@
|
|
|
1
1
|
# Contrek
|
|
2
|
-
Contrek is a Ruby gem powered by a <u>standalone C++17 core library</u> for fast contour tracing and edge detection in PNG images. It employs a **topological approach** to extract polygonal contours, representing shapes as a connected graph of shared endpoints. This ensures perfect adjacency and structural integrity for shape analysis and raster-to-vector workflows, such as PNG to SVG conversion, managed via libspng (0.7.4) with multithreading support.
|
|
2
|
+
Contrek is a Ruby gem powered by a <u>standalone C++17 core library</u> for fast contour tracing and edge detection in PNG images and raw memory streams. It employs a **topological approach** to extract polygonal contours, representing shapes as a connected graph of shared endpoints. This ensures perfect adjacency and structural integrity for shape analysis and raster-to-vector workflows, such as PNG to SVG conversion, managed via libspng (0.7.4) with multithreading support.
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
## About Contrek library
|
|
6
6
|
|
|
7
|
-
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.
|
|
7
|
+
Contrek (**con**tour **trek**king) simply scans your png/raw 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.
|
|
8
8
|
|
|
9
9
|
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
|
|
10
10
|

|
|
11
11
|
|
|
12
|
+
## 🚀 Why Contrek?
|
|
13
|
+
|
|
14
|
+
Contrek is a high-performance vectorization engine designed for massive datasets where memory efficiency and multi-core utilization are critical. Unlike traditional sequential contour tracers, Contrek is built on a **Stripe-Merging Architecture** that allows it to process images in independent segments and reconstruct the resulting polygons with perfect topological integrity.
|
|
15
|
+
|
|
16
|
+
### 1. Parallel Processing Power
|
|
17
|
+
Contrek is designed to saturate all available CPU cores. By partitioning a single image into stripes, it can assign each segment to a different thread.
|
|
18
|
+
* **The Result:** On multi-core systems, Contrek achieves unmatched extraction speeds by parallelizing the workload.
|
|
19
|
+
* **Scalability:** Performance scales directly with your hardware, making it the ideal choice for high-throughput processing servers.
|
|
20
|
+
|
|
21
|
+
### 2. Streamed Memory Management
|
|
22
|
+
Standard vectorization requires loading entire high-resolution images into RAM, which often leads to memory saturation.
|
|
23
|
+
* **The Solution:** Contrek enables **streamed processing** (see the C++ example in the repo). You can define a specific stripe height (buffer) and process the image incrementally, consuming only the memory allocated for that buffer.
|
|
24
|
+
* **The Benefit:** Vectorize massive, gigapixel-scale images or raw datasets directly from memory buffers. This allows for high-performance processing even on systems with limited memory that would otherwise fail.
|
|
25
|
+
|
|
26
|
+
### 3. Intelligent "Stitching" Technology
|
|
27
|
+
The core strength of Contrek is its **Topologically Consistent Merging** algorithm, which maintains a deep understanding of polygon connectivity across stripe boundaries.
|
|
28
|
+
* Whether you are optimizing for **speed** (parallel cores) or **memory** (sequential streaming), the output is always a seamless, continuous set of vector data, free from gaps or duplicates at the seams.
|
|
29
|
+
|
|
30
|
+
<table>
|
|
31
|
+
<tr>
|
|
32
|
+
<td><img src="docs/images/stripes/whole_0.png" alt="Contour tracing stripe 0" width="50%"></td>
|
|
33
|
+
<td rowspan="4" align="center">
|
|
34
|
+
<img src="docs/images/stripes/whole.png" alt="Contour tracing stripe 0" width="50%">
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
<tr>
|
|
38
|
+
<td><img src="docs/images/stripes/whole_256.png" alt="Contour tracing stripe 0" width="50%"></td>
|
|
39
|
+
</tr>
|
|
40
|
+
<tr>
|
|
41
|
+
<td><img src="docs/images/stripes/whole_512.png" alt="Contour tracing stripe 0" width="50%"></td>
|
|
42
|
+
</tr>
|
|
43
|
+
<tr>
|
|
44
|
+
<td><img src="docs/images/stripes/whole_768.png" alt="Contour tracing stripe 0" width="50%"></td>
|
|
45
|
+
</tr>
|
|
46
|
+
<tr><td colspan="2"><em><b>Left:</b> The image is split into 4 independent memory buffers (stripes).
|
|
47
|
+
<br><b>Right:</b> Contrek ensures <b>perfect topological continuity</b> during merging.</em>
|
|
48
|
+
<br><span style="color: #d32f2f;">■</span> <b>Red:</b> Outer contours |
|
|
49
|
+
<span style="color: #2e7d32;">■</span> <b>Dark Green:</b> Inner zones</td></tr>
|
|
50
|
+
</table>
|
|
12
51
|
|
|
13
52
|
## Prerequisites
|
|
14
53
|
|
|
@@ -86,6 +125,24 @@ You can also read base64 png images
|
|
|
86
125
|
png_bitmap = CPPRemotePngBitMap.new("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==")
|
|
87
126
|
```
|
|
88
127
|
|
|
128
|
+
You can process from a raw stream
|
|
129
|
+
```ruby
|
|
130
|
+
raw_bitmap = CPPRawBitMap.new
|
|
131
|
+
# set 20 as width, 30 as height, 4 as bytes per pixel and clears the content (true)
|
|
132
|
+
raw_bitmap.define(20,30,4,true)
|
|
133
|
+
# draws a polygon
|
|
134
|
+
4.upto(5) do |y|
|
|
135
|
+
5.upto(8) do |x|
|
|
136
|
+
raw_bitmap.draw_pixel(x, y, 1, 0, 0, 0);
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
not_matcher = CPPRGBNotMatcher.new(raw_bitmap.rgb_value_at(0, 0))
|
|
140
|
+
result = CPPPolygonFinder.new(raw_bitmap, not_matcher, nil,{compress: {uniq: true, linear: true}}).process_info
|
|
141
|
+
puts result.points.inspect
|
|
142
|
+
=>
|
|
143
|
+
[{:outer=>[{:x=>5, :y=>4}, {:x=>5, :y=>5}, {:x=>8, :y=>5}, {:x=>8, :y=>4}], :inner=>[]}]
|
|
144
|
+
```
|
|
145
|
+
|
|
89
146
|
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
147
|
|
|
91
148
|
```ruby
|
|
@@ -162,6 +219,26 @@ result = Contrek.contour!(
|
|
|
162
219
|
)
|
|
163
220
|
```
|
|
164
221
|
|
|
222
|
+
## Tracing Modes
|
|
223
|
+
|
|
224
|
+
Contrek provides two levels of precision for polygon generation to balance data density and topological accuracy.
|
|
225
|
+
|
|
226
|
+
### **Standard Mode** (Default)
|
|
227
|
+
Optimized for **speed** and **lightweight** output.
|
|
228
|
+
* **Behavior:** Traces outer boundaries while simplifying collinear segments.
|
|
229
|
+
* **Result:** Fewer vertices and smaller memory footprint.
|
|
230
|
+
|
|
231
|
+
### **Strict Mode** (`strict_bounds: true`)
|
|
232
|
+
Engineered for **Pixel-Perfect** precision.
|
|
233
|
+
* **Behavior:** Preserves every junction and internal connectivity change.
|
|
234
|
+
* **Result:** 100% topologically faithful geometry with no micro-gaps between adjacent polygons.
|
|
235
|
+
|
|
236
|
+
Below are two images illustrating the difference in tracing modes. In the first case, with **strict_bounds ON**, the anti-clockwise sequence includes two additional points, **H** and **I**, which trace the shape more accurately. In the second case, the transition between **G** and **H** is approximated, omitting the indentation.
|
|
237
|
+
| Strict Bounds ON | Strict Bounds OFF |
|
|
238
|
+
|:---:|:---:|
|
|
239
|
+
|  |  |
|
|
240
|
+
|
|
241
|
+
|
|
165
242
|
## Result
|
|
166
243
|
|
|
167
244
|
The result structure contains polygon coordinates and a set of metadata. Polygon coordinates can be accessed via:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -21,7 +21,10 @@ void run_test() {
|
|
|
21
21
|
// test_suite.test_c();
|
|
22
22
|
// test_suite.test_d();
|
|
23
23
|
// test_suite.test_e();
|
|
24
|
-
test_suite.test_f();
|
|
24
|
+
// test_suite.test_f();
|
|
25
|
+
// test_suite.test_g();
|
|
26
|
+
// test_suite.test_h();
|
|
27
|
+
test_suite.test_i();
|
|
25
28
|
std::cout << "compute time =" << cpu_timer.stop() << std::endl;
|
|
26
29
|
}
|
|
27
30
|
|
|
Binary file
|
|
@@ -14,17 +14,24 @@
|
|
|
14
14
|
#include <map>
|
|
15
15
|
#include <vector>
|
|
16
16
|
#include <string>
|
|
17
|
+
#include <cstring>
|
|
18
|
+
#include <algorithm>
|
|
19
|
+
#include <cstdio>
|
|
17
20
|
#include "polygon/finder/PolygonFinder.h"
|
|
18
21
|
#include "polygon/finder/concurrent/ClippedPolygonFinder.h"
|
|
19
22
|
#include "polygon/bitmaps/Bitmap.h"
|
|
20
23
|
#include "polygon/bitmaps/FastPngBitmap.h"
|
|
24
|
+
#include "polygon/bitmaps/RawBitmap.h"
|
|
21
25
|
#include "polygon/bitmaps/RemoteFastPngBitmap.h"
|
|
26
|
+
#include "polygon/bitmaps/spng.h"
|
|
22
27
|
#include "polygon/matchers/Matcher.h"
|
|
23
28
|
#include "polygon/matchers/RGBMatcher.h"
|
|
24
29
|
#include "polygon/matchers/RGBNotMatcher.h"
|
|
25
30
|
#include "polygon/matchers/ValueNotMatcher.h"
|
|
26
31
|
#include "polygon/finder/optionparser.h"
|
|
27
32
|
#include "polygon/finder/concurrent/Finder.h"
|
|
33
|
+
#include "polygon/finder/concurrent/HorizontalMerger.h"
|
|
34
|
+
#include "polygon/finder/concurrent/VerticalMerger.h"
|
|
28
35
|
#include "polygon/finder/concurrent/Sequence.h"
|
|
29
36
|
#include "polygon/finder/concurrent/Position.h"
|
|
30
37
|
#include "polygon/finder/Polygon.h"
|
|
@@ -44,6 +51,7 @@ void Tests::test_a()
|
|
|
44
51
|
Bitmap b(chunk, 16);
|
|
45
52
|
PolygonFinder pl(&b, &matcher, nullptr, &arguments);
|
|
46
53
|
ProcessResult *o = pl.process_info();
|
|
54
|
+
// o->print_polygons();
|
|
47
55
|
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};
|
|
48
56
|
std::vector<int> inner_array{7, 3, 10, 3, 10, 4, 7, 4};
|
|
49
57
|
std::vector<int> array_compare;
|
|
@@ -141,7 +149,7 @@ void Tests::test_d()
|
|
|
141
149
|
FastPngBitmap png_bitmap("../images/sample_10240x10240.png");
|
|
142
150
|
// FastPngBitmap png_bitmap("images/labyrinth.png");
|
|
143
151
|
std::cout << "image_w=" << png_bitmap.w() << " image_h=" << png_bitmap.h() << std::endl;
|
|
144
|
-
std::cout << "
|
|
152
|
+
std::cout << "image reading time =" << cpu_timer.stop() << std::endl;
|
|
145
153
|
|
|
146
154
|
int color = png_bitmap.value_at(0, 0);
|
|
147
155
|
std::cout << "color =" << color << std::endl;
|
|
@@ -169,7 +177,7 @@ void Tests::test_e()
|
|
|
169
177
|
Finder pl(2, &png_bitmap, ¬_matcher, &arguments);
|
|
170
178
|
ProcessResult *o = pl.process_info();
|
|
171
179
|
o->print_info();
|
|
172
|
-
std::cout << "polygons =" << o->groups << std::endl;
|
|
180
|
+
std::cout << "polygons = " << o->groups << std::endl;
|
|
173
181
|
delete o;
|
|
174
182
|
}
|
|
175
183
|
|
|
@@ -184,3 +192,177 @@ void Tests::test_f()
|
|
|
184
192
|
std::cout << "image_w=" << bitmap.w() << " image_h=" << bitmap.h() << std::endl;
|
|
185
193
|
std::cout << "load_error=" << bitmap.error() << std::endl;
|
|
186
194
|
}
|
|
195
|
+
|
|
196
|
+
void Tests::test_g()
|
|
197
|
+
{ RawBitmap raw_bitmap;
|
|
198
|
+
raw_bitmap.define(50, 50, 4);
|
|
199
|
+
|
|
200
|
+
// draw a polygon start_x = 5, start_y = 4, end_x = 8, end_y = 5
|
|
201
|
+
for (int y : {4, 5}) {
|
|
202
|
+
for (int x = 5; x <= 8; ++x) {
|
|
203
|
+
raw_bitmap.draw_pixel(x, y, 1, 0, 0);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
int color = raw_bitmap.rgb_value_at(0, 0);
|
|
208
|
+
std::cout << "color = " << color << std::endl;
|
|
209
|
+
RGBNotMatcher not_matcher(color);
|
|
210
|
+
|
|
211
|
+
std::vector<std::string> arguments = {"--versus=a", "--compress_uniq", "--number_of_tiles=2"};
|
|
212
|
+
Finder pl(2, &raw_bitmap, ¬_matcher, &arguments);
|
|
213
|
+
ProcessResult *o = pl.process_info();
|
|
214
|
+
// o->print_info();
|
|
215
|
+
// o->print_polygons();
|
|
216
|
+
std::cout << "polygons = " << o->groups << std::endl;
|
|
217
|
+
delete o;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
void Tests::test_h()
|
|
221
|
+
{ std::string left =
|
|
222
|
+
"0000000000" \
|
|
223
|
+
"0000000000" \
|
|
224
|
+
"00 " \
|
|
225
|
+
"00 " \
|
|
226
|
+
"00 " \
|
|
227
|
+
"00 " \
|
|
228
|
+
"00 " \
|
|
229
|
+
"0000000000" \
|
|
230
|
+
"0000000000";
|
|
231
|
+
std::vector<std::string> arguments = {"--versus=a", "--compress_uniq"};
|
|
232
|
+
ValueNotMatcher matcher(' ');
|
|
233
|
+
Bitmap b_left(left, 10);
|
|
234
|
+
PolygonFinder pl_left(&b_left, &matcher, nullptr, &arguments);
|
|
235
|
+
ProcessResult *left_result = pl_left.process_info();
|
|
236
|
+
|
|
237
|
+
std::string right =
|
|
238
|
+
"0000000000" \
|
|
239
|
+
"0000000000" \
|
|
240
|
+
" 0000" \
|
|
241
|
+
" 0000" \
|
|
242
|
+
" 0000" \
|
|
243
|
+
" 0000" \
|
|
244
|
+
" 0000" \
|
|
245
|
+
"0000000000" \
|
|
246
|
+
"0000000000";
|
|
247
|
+
Bitmap b_right(right, 10);
|
|
248
|
+
PolygonFinder pl_right(&b_right, &matcher, nullptr, &arguments);
|
|
249
|
+
ProcessResult *right_result = pl_right.process_info();
|
|
250
|
+
|
|
251
|
+
std::vector<std::string> merger_arguments = {};
|
|
252
|
+
HorizontalMerger hmerger(1, &arguments);
|
|
253
|
+
hmerger.add_tile(*left_result);
|
|
254
|
+
hmerger.add_tile(*right_result);
|
|
255
|
+
ProcessResult *merged_result = hmerger.process_info();
|
|
256
|
+
merged_result->print_polygons();
|
|
257
|
+
|
|
258
|
+
delete merged_result;
|
|
259
|
+
delete left_result;
|
|
260
|
+
delete right_result;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* In this example, PNG data is read by streaming into a user-defined buffer height.
|
|
264
|
+
Contrek scans each stripe and extracts the polygons. Finally, it merges all
|
|
265
|
+
polygons from every stripe as if they had been read from a single image and saves
|
|
266
|
+
the result polygons on a png image.
|
|
267
|
+
This approach allows for processing large PNG files on systems where memory
|
|
268
|
+
would otherwise be insufficient.
|
|
269
|
+
*/
|
|
270
|
+
void stream_png_image(const std::string& filepath, uint32_t stripe_height) {
|
|
271
|
+
std::vector<ProcessResult*> result_clones;
|
|
272
|
+
std::vector<std::string> varguments = {};
|
|
273
|
+
VerticalMerger vmerger(0, &varguments);
|
|
274
|
+
|
|
275
|
+
// opens image to stream
|
|
276
|
+
FILE* fp = fopen(filepath.c_str(), "rb");
|
|
277
|
+
if (!fp) {
|
|
278
|
+
std::cerr << "Unable open file: " << filepath << std::endl;
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// exams image
|
|
283
|
+
spng_ctx *ctx = spng_ctx_new(0);
|
|
284
|
+
spng_set_png_file(ctx, fp);
|
|
285
|
+
struct spng_ihdr ihdr;
|
|
286
|
+
if (spng_get_ihdr(ctx, &ihdr)) {
|
|
287
|
+
fclose(fp);
|
|
288
|
+
spng_ctx_free(ctx);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
uint32_t total_width = ihdr.width;
|
|
292
|
+
uint32_t total_height = ihdr.height;
|
|
293
|
+
|
|
294
|
+
// allocates stripe buffer
|
|
295
|
+
RawBitmap stripe_bitmap;
|
|
296
|
+
stripe_bitmap.define(total_width, stripe_height, 4, true);
|
|
297
|
+
RGBNotMatcher not_matcher(-1);
|
|
298
|
+
|
|
299
|
+
if (spng_decode_image(ctx, NULL, 0, SPNG_FMT_RGBA8, SPNG_DECODE_PROGRESSIVE)) {
|
|
300
|
+
fclose(fp);
|
|
301
|
+
spng_ctx_free(ctx);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
size_t row_size = static_cast<size_t>(total_width) * 4;
|
|
306
|
+
// main strpes loop
|
|
307
|
+
for (uint32_t current_y_offset = 0; current_y_offset < total_height; current_y_offset += stripe_height) {
|
|
308
|
+
int uncovered_height = total_height - current_y_offset;
|
|
309
|
+
|
|
310
|
+
// copy previous last line to the next new one (each contigue stripe must share one pixel scanline)
|
|
311
|
+
if (current_y_offset > 0) {
|
|
312
|
+
unsigned char* last_row_prev = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(stripe_height - 1));
|
|
313
|
+
unsigned char* first_row_curr = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(0));
|
|
314
|
+
std::memcpy(first_row_curr, last_row_prev, row_size);
|
|
315
|
+
}
|
|
316
|
+
// clears the part of the stripe wont be overwritten by png data
|
|
317
|
+
if (uncovered_height < stripe_height)
|
|
318
|
+
{ stripe_bitmap.draw_filled_rectangle(0, 1, total_width, stripe_height - 1, 255, 255, 255);
|
|
319
|
+
}
|
|
320
|
+
// decoding data directly in the stripe buffer
|
|
321
|
+
uint32_t lines_to_read = std::min(stripe_height, total_height - current_y_offset);
|
|
322
|
+
for (uint32_t y = (current_y_offset == 0 ? 0 : 1); y < lines_to_read; y++) {
|
|
323
|
+
unsigned char* row_ptr = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(y));
|
|
324
|
+
int ret = spng_decode_row(ctx, row_ptr, row_size);
|
|
325
|
+
if (ret != 0 && ret != SPNG_EOI) break;
|
|
326
|
+
}
|
|
327
|
+
// stripe contour tracing
|
|
328
|
+
std::vector<std::string> finder_arguments = {
|
|
329
|
+
"--versus=a",
|
|
330
|
+
"--strict_bounds" // <- this option is strictly needed when working with vertical merger
|
|
331
|
+
};
|
|
332
|
+
PolygonFinder polygon_finder(&stripe_bitmap, ¬_matcher, nullptr, &finder_arguments);
|
|
333
|
+
ProcessResult *result = polygon_finder.process_info();
|
|
334
|
+
if (result) {
|
|
335
|
+
std::cout << "Founds polygons: " << result->groups << std::endl;
|
|
336
|
+
ProcessResult* safe_result = result->clone();
|
|
337
|
+
result_clones.push_back(safe_result);
|
|
338
|
+
vmerger.add_tile(*safe_result);
|
|
339
|
+
delete result;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
std::cout << "Merging polygons ..." << std::endl;
|
|
344
|
+
ProcessResult *merged_result = vmerger.process_info();
|
|
345
|
+
std::cout << "Founds total polygons: " << merged_result->groups << std::endl;
|
|
346
|
+
|
|
347
|
+
if (merged_result) {
|
|
348
|
+
RawBitmap full_bitmap;
|
|
349
|
+
full_bitmap.define(total_width, total_height, 4, true);
|
|
350
|
+
full_bitmap.fill(255, 255, 255);
|
|
351
|
+
merged_result->draw_on_bitmap(full_bitmap);
|
|
352
|
+
std::cout << "Saving whole png ..." << std::endl;
|
|
353
|
+
if (full_bitmap.save_to_png("whole.png")) {
|
|
354
|
+
std::cout << "Png saved!" << std::endl;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
delete merged_result;
|
|
358
|
+
// frees memory
|
|
359
|
+
for (auto c : result_clones) {
|
|
360
|
+
delete c;
|
|
361
|
+
}
|
|
362
|
+
spng_ctx_free(ctx);
|
|
363
|
+
fclose(fp);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
void Tests::test_i() {
|
|
367
|
+
stream_png_image("../images/graphs_1024x1024.png", 300);
|
|
368
|
+
}
|
|
@@ -20,74 +20,38 @@
|
|
|
20
20
|
#include "FastPngBitmap.h"
|
|
21
21
|
#include "spng.h"
|
|
22
22
|
|
|
23
|
-
FastPngBitmap::FastPngBitmap(std::string filename) :
|
|
24
|
-
if (filename.length() > 0)
|
|
25
|
-
|
|
26
|
-
if (file.is_open())
|
|
27
|
-
|
|
23
|
+
FastPngBitmap::FastPngBitmap(std::string filename) : RawBitmap() {
|
|
24
|
+
if (filename.length() > 0) {
|
|
25
|
+
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
|
26
|
+
if (file.is_open()) {
|
|
27
|
+
std::streamsize size = file.tellg();
|
|
28
28
|
file.seekg(0, std::ios::beg);
|
|
29
29
|
std::vector<unsigned char> file_buffer(size);
|
|
30
|
-
if (!file.read(reinterpret_cast<char*>(file_buffer.data()), size))
|
|
31
|
-
|
|
30
|
+
if (!file.read(reinterpret_cast<char*>(file_buffer.data()), size)) {
|
|
31
|
+
this->png_error = -1;
|
|
32
32
|
} else {
|
|
33
33
|
spng_ctx *ctx = spng_ctx_new(0);
|
|
34
34
|
spng_set_png_buffer(ctx, file_buffer.data(), file_buffer.size());
|
|
35
35
|
struct spng_ihdr ihdr;
|
|
36
36
|
spng_get_ihdr(ctx, &ihdr);
|
|
37
|
-
this->width = ihdr.width;
|
|
38
|
-
this->height = ihdr.height;
|
|
39
37
|
size_t out_size;
|
|
40
38
|
spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &out_size);
|
|
41
|
-
this->
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
this->define(ihdr.width, ihdr.height, 4, false);
|
|
40
|
+
unsigned char* raw_ptr = this->image.get();
|
|
41
|
+
madvise(raw_ptr, out_size, MADV_HUGEPAGE);
|
|
42
|
+
int error = spng_decode_image(ctx, raw_ptr, out_size, SPNG_FMT_RGBA8, SPNG_DECODE_TRNS);
|
|
44
43
|
spng_ctx_free(ctx);
|
|
45
44
|
this->png_error = error;
|
|
46
45
|
}
|
|
47
46
|
} else {
|
|
48
|
-
throw std::runtime_error("Unable open file: " + filename);
|
|
47
|
+
throw std::runtime_error("Unable to open file: " + filename);
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
bool FastPngBitmap::pixel_match(int x, int y, Matcher *matcher)
|
|
54
|
-
{ int32_t index = ((y * width) + x) * 4;
|
|
55
|
-
unsigned int color;
|
|
56
|
-
unsigned char *red = &image[index];
|
|
57
|
-
std::memcpy(&color, red, 3);
|
|
58
|
-
if (typeid(*matcher) == typeid(RGBMatcher)) return((reinterpret_cast<RGBMatcher*>(matcher))->match(color));
|
|
59
|
-
if (typeid(*matcher) == typeid(RGBNotMatcher)) return((reinterpret_cast<RGBNotMatcher*>(matcher))->match(color));
|
|
60
|
-
return(false);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
int FastPngBitmap::w() {
|
|
64
|
-
return this->width;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
int FastPngBitmap::h() {
|
|
68
|
-
return this->height;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
52
|
int FastPngBitmap::error() {
|
|
72
53
|
return this->png_error;
|
|
73
54
|
}
|
|
74
|
-
char FastPngBitmap::value_at(int x, int y) {
|
|
75
|
-
return(0);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// source image format RGBA => returning uint ABGR
|
|
79
|
-
unsigned int FastPngBitmap::rgb_value_at(int x, int y) {
|
|
80
|
-
uint32_t index = (uint32_t(y) * width + x) * 4;
|
|
81
|
-
return *reinterpret_cast<const uint32_t*>(&image[index]);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const unsigned char* FastPngBitmap::get_row_ptr(int y) const {
|
|
85
|
-
return image.data() + (static_cast<size_t>(y) * static_cast<size_t>(width) * 4);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
int FastPngBitmap::get_bytes_per_pixel() const {
|
|
89
|
-
return 4; // RGBA
|
|
90
|
-
}
|
|
91
55
|
|
|
92
56
|
void FastPngBitmap::loadFile(std::vector<unsigned char>& buffer, const std::string& filename) // designed for loading files from hard disk in an std::vector
|
|
93
57
|
{ std::ifstream file(filename.c_str(), std::ios::in|std::ios::binary|std::ios::ate);
|
|
@@ -11,29 +11,20 @@
|
|
|
11
11
|
#include <vector>
|
|
12
12
|
#include <cstddef>
|
|
13
13
|
#include <string>
|
|
14
|
-
#include "
|
|
14
|
+
#include "RawBitmap.h"
|
|
15
15
|
|
|
16
16
|
class RGBMatcher;
|
|
17
17
|
|
|
18
18
|
static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
19
19
|
|
|
20
|
-
class FastPngBitmap : public
|
|
20
|
+
class FastPngBitmap : public RawBitmap {
|
|
21
21
|
public:
|
|
22
22
|
explicit FastPngBitmap(std::string filename);
|
|
23
|
-
bool pixel_match(int x, int y, Matcher *matcher);
|
|
24
|
-
int h();
|
|
25
|
-
int w();
|
|
26
23
|
virtual int error();
|
|
27
|
-
char value_at(int x, int y);
|
|
28
|
-
unsigned int rgb_value_at(int x, int y);
|
|
29
|
-
const unsigned char* get_row_ptr(int y) const;
|
|
30
|
-
int get_bytes_per_pixel() const;
|
|
31
24
|
|
|
32
25
|
protected:
|
|
33
26
|
std::vector<unsigned char> buffer;
|
|
34
27
|
std::vector<unsigned char> base64_decode(std::string &encoded_string);
|
|
35
|
-
std::vector<unsigned char> image;
|
|
36
|
-
unsigned int width, height;
|
|
37
28
|
int png_error;
|
|
38
29
|
int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true);
|
|
39
30
|
|