Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for conversion from python bytes #79

Merged
merged 5 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 93 additions & 6 deletions cmdapp/src/python.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::*;
use pyo3::prelude::*;
use image::{io::Reader, ImageFormat};
use pyo3::{exceptions::PyException, prelude::*};
use std::io::{BufReader, Cursor};
use std::path::PathBuf;
use visioncortex::PathSimplifyMode;

Expand All @@ -23,6 +25,93 @@ fn convert_image_to_svg_py(
let input_path = PathBuf::from(image_path);
let output_path = PathBuf::from(out_path);

let config = construct_config(
colormode,
hierarchical,
mode,
filter_speckle,
color_precision,
layer_difference,
corner_threshold,
length_threshold,
max_iterations,
splice_threshold,
path_precision,
);

convert_image_to_svg(&input_path, &output_path, config).unwrap();
Ok(())
}

#[pyfunction]
fn convert_py(
img_bytes: Vec<u8>,
img_format: Option<&str>, // Format of the image. If not provided, the image format will be guessed based on its contents.
Copy link
Member

@tyt2y3 tyt2y3 Apr 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are the possible image format? e.g. rgba8888 ?

can you give some examples?

also, I'd say name it convert_raw_image_to_svg

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are image file formats supported by the image crate, listed here

colormode: Option<&str>, // "color" or "binary"
hierarchical: Option<&str>, // "stacked" or "cutout"
mode: Option<&str>, // "polygon", "spline", "none"
filter_speckle: Option<usize>, // default: 4
color_precision: Option<i32>, // default: 6
layer_difference: Option<i32>, // default: 16
corner_threshold: Option<i32>, // default: 60
length_threshold: Option<f64>, // in [3.5, 10] default: 4.0
max_iterations: Option<usize>, // default: 10
splice_threshold: Option<i32>, // default: 45
path_precision: Option<u32>, // default: 8
) -> PyResult<String> {
let config = construct_config(
colormode,
hierarchical,
mode,
filter_speckle,
color_precision,
layer_difference,
corner_threshold,
length_threshold,
max_iterations,
splice_threshold,
path_precision,
);
let mut img_reader = Reader::new(BufReader::new(Cursor::new(img_bytes)));
let img_format = img_format.and_then(|ext_name| ImageFormat::from_extension(ext_name));
let img = match img_format {
Some(img_format) => {
img_reader.set_format(img_format);
img_reader.decode()
}
None => img_reader
.with_guessed_format()
.map_err(|_| PyException::new_err("Unrecognized image format. "))?
.decode(),
};
let img = match img {
Ok(img) => img.to_rgba8(),
Err(_) => return Err(PyException::new_err("Failed to decode img_bytes. ")),
};
let (width, height) = (img.width() as usize, img.height() as usize);
let img = ColorImage {
pixels: img.as_raw().to_vec(),
width,
height,
};
let svg = convert(img, config)
.map_err(|_| PyException::new_err("Failed to convert the image. "))?;
Ok(format!("{}", svg))
}

fn construct_config(
colormode: Option<&str>,
hierarchical: Option<&str>,
mode: Option<&str>,
filter_speckle: Option<usize>,
color_precision: Option<i32>,
layer_difference: Option<i32>,
corner_threshold: Option<i32>,
length_threshold: Option<f64>,
max_iterations: Option<usize>,
splice_threshold: Option<i32>,
path_precision: Option<u32>,
) -> Config {
// TODO: enforce color mode with an enum so that we only
// accept the strings 'color' or 'binary'
let color_mode = match colormode.unwrap_or("color") {
Expand Down Expand Up @@ -52,7 +141,7 @@ fn convert_image_to_svg_py(
let splice_threshold = splice_threshold.unwrap_or(45);
let max_iterations = max_iterations.unwrap_or(10);

let config = Config {
Config {
color_mode,
hierarchical,
filter_speckle,
Expand All @@ -65,15 +154,13 @@ fn convert_image_to_svg_py(
splice_threshold,
path_precision,
..Default::default()
};

convert_image_to_svg(&input_path, &output_path, config).unwrap();
Ok(())
}
}

/// A Python module implemented in Rust.
#[pymodule]
fn vtracer(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(convert_image_to_svg_py, m)?)?;
m.add_function(wrap_pyfunction!(convert_py, m)?)?;
Ok(())
}
2 changes: 1 addition & 1 deletion cmdapp/vtracer/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .vtracer import convert_image_to_svg_py
from .vtracer import convert_image_to_svg_py, convert_py
17 changes: 17 additions & 0 deletions cmdapp/vtracer/vtracer.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,20 @@ def convert_image_to_svg_py(image_path: str,
path_precision: Optional[int] = None, # default: 8
) -> None:
...

def convert_py(
img_bytes: bytes,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this indentation looks too deep haha. may be you are aligning with above?

img_format: Optional[str] = None, # Format of the image. If not provided, the image format will be guessed based on its contents.
colormode: Optional[str] = None, # ["color"] or "binary"
hierarchical: Optional[str] = None, # ["stacked"] or "cutout"
mode: Optional[str] = None, # ["spline"], "polygon", "none"
filter_speckle: Optional[int] = None, # default: 4
color_precision: Optional[int] = None, # default: 6
layer_difference: Optional[int] = None, # default: 16
corner_threshold: Optional[int] = None, # default: 60
length_threshold: Optional[float] = None, # in [3.5, 10] default: 4.0
max_iterations: Optional[int] = None, # default: 10
splice_threshold: Optional[int] = None, # default: 45
path_precision: Optional[int] = None, # default: 8
) -> str:
...