img2svg 0.1.9

A rust native image to SVG converter in CLI/MCP/Library
Documentation
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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
//! img2svg - A high-quality image to SVG converter library
//!
//! This library provides functionality to convert raster images (PNG, JPEG, etc.)
//! into scalable vector graphics (SVG) format.
//!
//! ## Features
//!
//! - **Color quantization** using median-cut algorithm
//! - **Marching squares** contour tracing for accurate shape detection
//! - **Ramer-Douglas-Peucker** simplification for clean paths
//! - **Gaussian smoothing** for natural curves
//!
//! ## Example
//!
//! ```rust,no_run
//! use img2svg::{convert, ConversionOptions};
//! use std::path::Path;
//!
//! let options = ConversionOptions {
//!     num_colors: 16,
//!     smooth_level: 5,
//!     ..Default::default()
//! };
//!
//! convert(Path::new("input.png"), Path::new("output.svg"), &options)
//!     .expect("Conversion failed");
//! ```

/// Cubic Bézier curve fitting with Newton-Raphson reparameterization.
pub mod bezier_fitter;
/// Sobel edge detection for gradient-based edge maps.
pub mod edge_detector;
/// K-means++ initialization and edge-aware color quantization.
pub mod enhanced_quantizer;
/// Enhanced vectorization pipeline: marching squares → Bézier curves.
pub mod enhanced_vectorizer;
/// Potrace-style despeckle: remove small color islands before contour extraction.
pub mod despeckle;
/// Centerline tracing for line art mode.
pub mod centerline_tracer;
/// EPS/PDF/AI vector export.
pub mod eps_generator;
/// Logo fill + outline separation.
pub mod logo_pipeline;
/// HTML parameter tuning grid generator.
pub mod tune;
/// Linear and radial gradient detection for smoother SVG gradient fills.
pub mod gradient_detector;
/// Comprehensive image processing filters (noise reduction, edge enhancement,
/// contrast adjustment, thresholding, morphology, color operations, effects).
pub mod image_filters;
/// Image loading, auto-resize, and median-cut color quantization.
pub mod image_processor;
/// Visvalingam-Whyatt simplification with corner preservation.
pub mod path_simplifier;
/// LUT bilateral filter and color reduction for photo preprocessing.
pub mod preprocessor;
/// Background color detection via border-pixel frequency.
pub mod region_extractor;
/// SVG generation with compact output and alpha channel support.
pub mod svg_generator;
/// Original pipeline: marching squares, Gaussian smoothing, RDP simplification.
pub mod vectorizer;

/// Re-export anyhow Result for convenience.
pub use anyhow::Result;
/// Re-export enhanced vectorization pipeline types and functions.
pub use enhanced_vectorizer::{
    generate_enhanced_svg, generate_enhanced_svg_to, vectorize_enhanced, write_enhanced_svg,
    EnhancedOptions, EnhancedPath, EnhancedVectorData,
};
/// Re-export image filter functions and types.
pub use gradient_detector::{
    detect_gradient, detect_linear_gradient, detect_radial_gradient, DetectedGradient,
    LinearGradient, RadialGradient,
};
pub use image_filters::{
    mean_shift_filter, median_filter, prewitt_edge_detection, quantize_from_labels,
    roberts_edge_detection, slic_superpixels, watershed_segment, weighted_median_filter,
    AdaptiveThreshold, AdaptiveToneMapper, CannyEdgeDetector, ColorOps, ColorTemperature,
    EmbossFilter, GammaCorrection, HighBoostFilter, HistogramEqualizer, LaplacianSharpen,
    MorphologyKind, MorphologyOp, NonLocalMeans, OtsuThreshold, PeronaMalik, Saturation,
    SepiaFilter, SmartThreshold, UnsharpMask, VignetteFilter, VintageFilter, CLAHE, HSL, LAB,
};
pub use eps_generator::{
    write_enhanced_ai, write_enhanced_eps, write_enhanced_output, write_enhanced_pdf,
    write_vectorized_eps, EnhancedOutputFormat,
};
/// Re-export image loading and quantization functions.
pub use image_processor::{
    cmyk_to_rgb, detect_color_space, image_data_from_cmyk, load_animated_gif, load_image,
    quantize_colors, ColorSpace, GifFrame, ImageData,
};
/// Re-export photo preprocessing functions.
pub use despeckle::despeckle;
/// Re-export SVG generation functions.
pub use svg_generator::{
    generate_svg, generate_svg_advanced, generate_svg_to, write_multi_path_to, write_subpath_to,
};
/// Re-export original vectorization pipeline types and functions.
pub use vectorizer::{vectorize, Curve, Point, VectorizedData};

/// Options for image to SVG conversion
#[derive(Debug, Clone)]
pub struct ConversionOptions {
    /// Number of colors for quantization (default: 16)
    pub num_colors: usize,
    /// Edge detection threshold 0.0-1.0 (default: 0.1)
    pub threshold: f64,
    /// Path smoothing level 0-10 (default: 5)
    pub smooth_level: u8,
    /// Enable hierarchical decomposition (default: false)
    pub hierarchical: bool,
    /// Use advanced SVG generation (default: false)
    pub advanced: bool,
}

impl Default for ConversionOptions {
    fn default() -> Self {
        Self {
            num_colors: 16,
            threshold: 0.1,
            smooth_level: 5,
            hierarchical: false,
            advanced: false,
        }
    }
}

/// Convert an image file to SVG
///
/// # Arguments
///
/// * `input_path` - Path to the input image file
/// * `output_path` - Path to the output SVG file
/// * `options` - Conversion options
///
/// # Example
///
/// ```rust,no_run
/// use img2svg::{convert, ConversionOptions};
/// use std::path::Path;
///
/// let options = ConversionOptions::default();
/// convert(Path::new("input.png"), Path::new("output.svg"), &options)?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn convert(
    input_path: &std::path::Path,
    output_path: &std::path::Path,
    options: &ConversionOptions,
) -> Result<()> {
    // Load the image
    let image_data = load_image(input_path)?;

    // Vectorize the image
    let vectorized_data = vectorize(
        &image_data,
        options.num_colors,
        options.threshold,
        options.smooth_level,
        options.hierarchical,
    )?;

    // Generate SVG output
    if options.advanced {
        generate_svg_advanced(&vectorized_data, output_path)?;
    } else {
        generate_svg(&vectorized_data, output_path)?;
    }

    Ok(())
}

/// Convert image data directly to SVG string
///
/// This is useful when you have image data in memory and want to get
/// the SVG content as a string without writing to a file.
///
/// # Arguments
///
/// * `image_data` - The image data to convert
/// * `options` - Conversion options
///
/// # Returns
///
/// A String containing the SVG content
pub fn convert_to_svg_string(
    image_data: &ImageData,
    options: &ConversionOptions,
) -> Result<String> {
    let vectorized_data = vectorize(
        image_data,
        options.num_colors,
        options.threshold,
        options.smooth_level,
        options.hierarchical,
    )?;

    let mut buffer = Vec::new();
    svg_generator::generate_svg_to(&vectorized_data, &mut buffer)?;
    Ok(String::from_utf8(buffer)?)
}

/// Convert an animated GIF to a vector of per-frame SVG strings.
///
/// Each frame is processed independently using the original pipeline.
/// Returns `(svg_strings, delays_ms)` so callers can assemble an
/// animated SVG or output individual files.
pub fn convert_animated_gif(
    frames: &[GifFrame],
    options: &ConversionOptions,
) -> Result<(Vec<String>, Vec<u32>)> {
    let mut svgs = Vec::with_capacity(frames.len());
    let mut delays = Vec::with_capacity(frames.len());

    for frame in frames {
        let svg = convert_to_svg_string(&frame.image_data, options)?;
        svgs.push(svg);
        delays.push(frame.delay_ms);
    }

    Ok((svgs, delays))
}

/// Check whether a file path has a supported raster image extension.
pub fn is_supported_image(path: &std::path::Path) -> bool {
    if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
        matches!(
            ext.to_lowercase().as_str(),
            "bmp" | "png" | "jpg" | "jpeg" | "gif" | "ico" | "tiff" | "tif" | "webp" | "pnm"
                | "tga" | "dds" | "farbfeld"
        )
    } else {
        false
    }
}

/// Result of converting one file in a batch run.
#[derive(Debug, Clone)]
pub struct BatchConvertEntry {
    pub input: std::path::PathBuf,
    pub output: std::path::PathBuf,
    pub success: bool,
    pub error: Option<String>,
}

/// Summary of a batch directory conversion.
#[derive(Debug, Clone)]
pub struct BatchConvertSummary {
    pub total: usize,
    pub converted: usize,
    pub errors: usize,
    pub entries: Vec<BatchConvertEntry>,
}

/// Convert all supported images in `input_dir` to vector files in `output_dir`.
///
/// Matches CLI batch mode (`img2svg -i dir/ -o out/`). Creates `output_dir` if missing.
pub fn batch_convert_enhanced(
    input_dir: &std::path::Path,
    output_dir: &std::path::Path,
    options: &EnhancedOptions,
    format: EnhancedOutputFormat,
    max_size: u32,
) -> Result<BatchConvertSummary> {
    std::fs::create_dir_all(output_dir)?;

    let entries: Vec<_> = std::fs::read_dir(input_dir)?
        .filter_map(|e| e.ok())
        .filter(|e| e.path().is_file() && is_supported_image(&e.path()))
        .collect();

    let total = entries.len();
    let mut converted = 0usize;
    let mut errors = 0usize;
    let mut results = Vec::with_capacity(total);

    for entry in entries {
        let input_path = entry.path();
        let stem = input_path
            .file_stem()
            .unwrap_or_default()
            .to_string_lossy()
            .into_owned();
        let mut output_path = output_dir.join(&stem);
        output_path.set_extension(format.extension());

        let outcome = (|| -> Result<()> {
            let image_data = load_image(&input_path)?;
            let image_data = image_processor::resize_if_needed(image_data, max_size);
            convert_enhanced_data(&image_data, &output_path, options, Some(format))?;
            Ok(())
        })();

        match outcome {
            Ok(()) => {
                converted += 1;
                results.push(BatchConvertEntry {
                    input: input_path,
                    output: output_path,
                    success: true,
                    error: None,
                });
            }
            Err(e) => {
                errors += 1;
                results.push(BatchConvertEntry {
                    input: input_path,
                    output: output_path,
                    success: false,
                    error: Some(e.to_string()),
                });
            }
        }
    }

    Ok(BatchConvertSummary {
        total,
        converted,
        errors,
        entries: results,
    })
}

/// Convert in-memory image data with the enhanced Bézier pipeline.
///
/// Output format defaults to the `output_path` extension (`.svg` default).
/// Pass `format` to override (e.g. write EPS to a path ending in `.svg`).
/// Returns vectorization metadata after writing the output file.
pub fn convert_enhanced_data(
    image_data: &ImageData,
    output_path: &std::path::Path,
    options: &EnhancedOptions,
    format: Option<EnhancedOutputFormat>,
) -> Result<EnhancedVectorData> {
    let fmt = format.unwrap_or_else(|| EnhancedOutputFormat::from_path(output_path));
    let data = vectorize_enhanced(image_data, options)?;
    write_enhanced_output(&data, output_path, fmt)?;
    Ok(data)
}

/// Convert an image file with the enhanced Bézier pipeline (CLI default).
///
/// This is the high-level counterpart to [`convert`] for the default pipeline.
/// Output format is inferred from `output_path` extension (`.svg` default).
///
/// # Example
///
/// ```rust,no_run
/// use img2svg::{convert_enhanced, EnhancedOptions};
/// use std::path::Path;
///
/// let options = EnhancedOptions {
///     num_colors: 16,
///     ..Default::default()
/// };
/// convert_enhanced(Path::new("input.png"), Path::new("output.svg"), &options)?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn convert_enhanced(
    input_path: &std::path::Path,
    output_path: &std::path::Path,
    options: &EnhancedOptions,
) -> Result<()> {
    let image_data = load_image(input_path)?;
    convert_enhanced_data(&image_data, output_path, options, None)?;
    Ok(())
}

/// Convert in-memory image data to an enhanced SVG string.
///
/// Same pipeline as [`convert_enhanced`] but returns SVG markup instead of writing a file.
pub fn convert_enhanced_to_svg_string(
    image_data: &ImageData,
    options: &EnhancedOptions,
) -> Result<String> {
    let data = vectorize_enhanced(image_data, options)?;
    Ok(generate_enhanced_svg(&data))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_conversion_options_default() {
        let options = ConversionOptions::default();
        assert_eq!(options.num_colors, 16);
        assert_eq!(options.threshold, 0.1);
        assert_eq!(options.smooth_level, 5);
        assert!(!options.hierarchical);
        assert!(!options.advanced);
    }

    #[test]
    fn test_is_supported_image() {
        assert!(is_supported_image(std::path::Path::new("a.png")));
        assert!(is_supported_image(std::path::Path::new("a.JPG")));
        assert!(!is_supported_image(std::path::Path::new("readme.txt")));
    }

    #[test]
    fn test_convert_enhanced_to_svg_string() {
        let mut pixels = Vec::new();
        for _ in 0..(20 * 20) {
            pixels.push(rgb::RGBA8::new(200, 50, 50, 255));
        }
        let image = ImageData {
            width: 20,
            height: 20,
            pixels,
        };
        let opts = EnhancedOptions {
            num_colors: 4,
            preprocess: false,
            ..Default::default()
        };
        let svg = convert_enhanced_to_svg_string(&image, &opts).expect("convert");
        assert!(svg.contains("<svg"));
        assert!(svg.contains("<path") || svg.contains("<rect"));
    }
}