1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
//!
//! List files in Embroidery API (action part).
//!
mod private
{
use crate::*;
pub use actions::
{
Result,
Error,
common::
{
euclid_difference,
read_image,
write_svg,
background_color,
find_unused_color_in_image,
should_key_image
}
};
use commands::raster::vectorize::clusters::{ CLIArgs, Config, Hierarchical };
use image::DynamicImage;
use svg::SvgFile;
use visioncortex::color_clusters;
/// Represents a report for the raster to vector conversion using the color method.
///
/// This struct contains details about the conversion process, including the size of the input image,
/// whether keying was used, and the total number of clusters generated during the conversion.
///
/// # Fields
/// * `image_size` - An array containing the width and height of the input image.
/// * `keying_used` - A boolean indicating whether keying was used during the conversion process.
/// * `total_clusters` - The total number of clusters generated during the conversion process.
///
#[ derive( Debug, Default ) ]
pub struct Report
{
image_size : [ usize; 2 ],
keying_used : bool,
total_clusters : usize,
/// The time taken for the conversion process in seconds.
pub work_time : f32,
}
impl std::fmt::Display for Report
{
fn fmt( &self, f: &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result
{
write!( f, "Raster vectorize clusters report:\n" )?;
write!( f, "Working time: {}\n", self.work_time )?;
write!( f, "Keying used: {}\n", self.keying_used )?;
write!( f, "Total clusters used: {}\n", self.total_clusters )?;
Ok( () )
}
}
/// Executes the raster vectorize command using the color method.
///
/// This asynchronous function reads a raster image from the specified input, converts it to an SVG file
/// using the provided configuration, and saves the resulting SVG file to the specified output location.
/// It also generates a report detailing the conversion process.
///
/// # Arguments
/// * `args` - A `CLIArgs` struct containing the input/output paths and configuration parameters.
///
/// # Returns
/// * `Result<()>` - An empty result if the operation is successful, or an error if any step fails.
///
/// # Errors
/// This function will return an error if the image reading, conversion, or SVG writing fails.
pub async fn action( args : CLIArgs ) -> Result < Report >
{
let start_work_time = std::time::Instant::now();
let img = read_image( &args.io )?;
let mut report = Report::default();
let svg = convert_to_vector( img, &args.config, &mut report )?;
// Save on disk
write_svg( &args.io, &svg )?;
report.work_time = start_work_time.elapsed().as_secs_f32();
Ok( report )
}
/// Converts a raster image into an SVG file using the provided configuration.
///
/// This function takes a raster image, processes it according to the provided configuration,
/// and outputs the result as an SVG file. It also generates a report detailing the conversion process.
///
/// # Arguments
/// * `img` - The input raster image to be converted.
/// * `config` - Configuration parameters for the vectorization process.
/// * `report` - A mutable reference to a `Report` struct where the conversion details will be stored.
///
/// # Returns
/// * `Result<SvgFile>` - A result containing the generated SVG file or an error if the conversion fails.
///
/// # Errors
/// This function will return an error if the image reading, processing, or SVG writing fails.
pub fn convert_to_vector( img : DynamicImage, config : &Config, report : &mut Report ) -> Result< SvgFile >
{
let img = img.into_rgba8();
let ( width, height ) = ( img.width() as usize, img.height() as usize );
report.image_size = [ width, height ];
// Convert to vtrace image
let mut img = visioncortex::ColorImage
{
pixels : img.into_vec(),
width : width,
height : height
};
let mask = 0xFF;
let bg_color = if config.remove_background
{
if config.background_color.is_some()
{
config.background_color
.map( | c | visioncortex::Color::new( c[ 0 ], c[ 1 ], c[ 2 ] ) )
}
else
{
background_color( &img, mask )
.map( | c | visioncortex::Color::new( c[ 0 ], c[ 1 ], c[ 2 ] ) )
}
}
else
{
None
};
// If an image has a lot of transparent pixels, key them out with a color not present in the image
let key_color =
if Hierarchical::Stacked == config.hierarchical && bg_color.is_some()
{
report.keying_used = true;
let bg_color = bg_color.unwrap();
for y in 0..height
{
for x in 0..width
{
let c = img.get_pixel( x, y );
if c.a == 0 || euclid_difference( bg_color, c ) < config.background_similarity
{
img.set_pixel( x, y, &bg_color );
}
}
}
bg_color
}
else if should_key_image( &img )
{
report.keying_used = true;
let key_color = find_unused_color_in_image( &img )?;
for y in 0..height
{
for x in 0..width
{
if img.get_pixel( x, y ).a == 0
{
img.set_pixel( x, y, &key_color );
}
}
}
key_color
}
else
{
// No keying
visioncortex::Color::default()
};
// Distance between layers
let gradient_step = config.gradient_step as i32;
let min_cluster_area = config.filter_speckle * config.filter_speckle;
let runner = color_clusters::Runner::new
(
color_clusters::RunnerConfig
{
diagonal : gradient_step == 0,
hierarchical : color_clusters::HIERARCHICAL_MAX,
batch_size : 25600,
// Remove clasters smaller than the filter_speckle_area
good_min_area : min_cluster_area,
good_max_area : ( width * height ),
// color precision
is_same_color_a : 8 - config.color_precision as i32,
is_same_color_b : 1,
deepen_diff : gradient_step,
hollow_neighbours : 1,
key_color,
keying_action : match config.hierarchical
{
Hierarchical::Stacked => color_clusters::KeyingAction::Discard,
Hierarchical::Cutout => color_clusters::KeyingAction::Keep
}
},
img.clone()
);
// Create hierarchical sutrcture of clusters, according to
// https://www.visioncortex.org/impression-docs
let mut clusters = runner.run();
match config.hierarchical
{
Hierarchical::Stacked => { },
Hierarchical::Cutout =>
{
let view = clusters.view();
let image = view.to_color_image();
let runner = color_clusters::Runner::new
(
color_clusters::RunnerConfig
{
diagonal: false,
hierarchical: 64,
batch_size: 25600,
good_min_area: 0,
good_max_area: ( image.width * image.height ) as usize,
is_same_color_a: 0,
is_same_color_b: 1,
deepen_diff: 0,
hollow_neighbours: 0,
key_color,
keying_action: color_clusters::KeyingAction::Discard,
},
image,
);
clusters = runner.run();
}
}
let view = clusters.view();
let mut svg = SvgFile::new( width, height, Some( 2 ) );
let mut total_cluster = 0;
for id in view.clusters_output.iter().rev()
{
total_cluster += 1;
let cluster = view.get_cluster( *id );
if Hierarchical::Cutout == config.hierarchical && config.remove_background
{
let bg_color = bg_color.unwrap();
if euclid_difference( bg_color, cluster.residue_color() ) < config.background_similarity
{
continue;
}
}
// Convert cluster to path
let path = cluster.to_compound_path
(
&view,
false,
config.mode.into(),
config.corner_threshold.to_radians(),
config.segment_length,
10,
config.splice_threshold.to_radians()
);
// Add path to svg
svg.add_path( path, cluster.residue_color() );
}
report.total_clusters = total_cluster;
Ok( svg )
}
}
crate::mod_interface!
{
own use
{
action,
convert_to_vector,
Error,
Result,
Report
};
}