vtracer-0.6.15/PKG-INFO0000644000000103321046102023000077570ustar Metadata-Version: 2.4 Name: vtracer Version: 0.6.15 Classifier: Programming Language :: Rust Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy License-File: LICENSE Summary: Python bindings for the Rust Vtracer raster-to-vector library Home-Page: http://www.visioncortex.org/vtracer Author-email: Chris Tsang License-Expression: MIT Requires-Python: >=3.7 Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

VTracer: Python Binding

Raster to Vector Graphics Converter built on top of visioncortex

Article | Demo | Download

Built with 🦀 by The Vision Cortex Research Group
## Introduction visioncortex VTracer is an open source software to convert raster images (like jpg & png) into vector graphics (svg). It can vectorize graphics and photographs and trace the curves to output compact vector files. Comparing to [Potrace](http://potrace.sourceforge.net/) which only accept binarized inputs (Black & White pixmap), VTracer has an image processing pipeline which can handle colored high resolution scans. Comparing to Adobe Illustrator's [Image Trace](https://helpx.adobe.com/illustrator/using/image-trace.html), VTracer's output is much more compact (less shapes) as we adopt a stacking strategy and avoid producing shapes with holes. VTracer is originally designed for processing high resolution scans of historic blueprints up to gigapixels. At the same time, VTracer can also handle low resolution pixel art, simulating `image-rendering: pixelated` for retro game artworks. A technical description of the algorithm is on [visioncortex.org/vtracer-docs](//www.visioncortex.org/vtracer-docs). ## Install (Python) ```shell pip install vtracer ``` ### Usage (Python) ```python import vtracer input_path = "/path/to/some_file.jpg" output_path = "/path/to/some_file.vtracer.jpg" # Minimal example: use all default values, generate a multicolor SVG vtracer.convert_image_to_svg_py(inp, out) # Single-color example. Good for line art, and much faster than full color: vtracer.convert_image_to_svg_py(inp, out, colormode='binary') # Convert from raw image bytes input_img_bytes: bytes = get_bytes() # e.g. reading bytes from a file or a HTTP request body svg_str: str = vtracer.convert_raw_image_to_svg(input_img_bytes, img_format='jpg') # Convert from RGBA image pixels from PIL import Image img = Image.open(input_path).convert('RGBA') pixels: list[tuple[int, int, int, int]] = list(img.getdata()) svg_str: str = vtracer.convert_pixels_to_svg(pixels, img.size) # All the bells & whistles, also applicable to convert_raw_image_to_svg and convert_pixels_to_svg. vtracer.convert_image_to_svg_py(inp, out, colormode = 'color', # ["color"] or "binary" hierarchical = 'stacked', # ["stacked"] or "cutout" mode = 'spline', # ["spline"] "polygon", or "none" filter_speckle = 4, # default: 4 color_precision = 6, # default: 6 layer_difference = 16, # default: 16 corner_threshold = 60, # default: 60 length_threshold = 4.0, # in [3.5, 10] default: 4.0 max_iterations = 10, # default: 10 splice_threshold = 45, # default: 45 path_precision = 3 # default: 8 ) ``` ## Rust Library The (Rust) library can be found on [crates.io/vtracer](//crates.io/crates/vtracer) and [crates.io/vtracer-webapp](//crates.io/crates/vtracer-webapp). vtracer-0.6.15/Cargo.lock0000644000000514671046102023000106050ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "adler32" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "ansi_term" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ "winapi", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", "bitflags 1.3.2", "strsim", "textwrap", "unicode-width", "vec_map", ] [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "console_error_panic_hook" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ "cfg-if 1.0.4", "wasm-bindgen", ] [[package]] name = "console_log" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" dependencies = [ "log", "wasm-bindgen", "web-sys", ] [[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if 1.0.4", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "deflate" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" dependencies = [ "adler32", "byteorder", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" dependencies = [ "getrandom", ] [[package]] name = "flo_curves" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c227ffb4f98baa9073c033e23243089d0ed3dd1f5cd620894452074db48a29b" dependencies = [ "itertools", "roots", "smallvec", ] [[package]] name = "getrandom" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if 1.0.4", "js-sys", "libc", "wasi", "wasm-bindgen", ] [[package]] name = "gif" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" dependencies = [ "color_quant", "weezl", ] [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "image" version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" dependencies = [ "bytemuck", "byteorder", "color_quant", "gif", "jpeg-decoder", "num-iter", "num-rational", "num-traits", "png", "scoped_threadpool", "tiff", ] [[package]] name = "indoc" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" [[package]] name = "itertools" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jpeg-decoder" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" dependencies = [ "rayon", ] [[package]] name = "js-sys" version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "miniz_oxide" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" dependencies = [ "adler32", ] [[package]] name = "miniz_oxide" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", "autocfg", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-rational" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if 1.0.4", "libc", "redox_syscall", "smallvec", "windows-link", ] [[package]] name = "png" version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" dependencies = [ "bitflags 1.3.2", "crc32fast", "deflate", "miniz_oxide 0.3.7", ] [[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" dependencies = [ "cfg-if 1.0.4", "indoc", "libc", "memoffset", "parking_lot", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-build-config" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" dependencies = [ "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-macros" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn 1.0.109", ] [[package]] name = "pyo3-macros-backend" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "quote" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.11.0", ] [[package]] name = "roots" version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84348444bd7ad45729d0c49a4240d7cdc11c9d512c06c5ad1835c1ad4acda6db" [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "scoped_threadpool" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "serde_json" version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", "serde", "serde_core", "zmij", ] [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "tiff" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" dependencies = [ "jpeg-decoder", "miniz_oxide 0.4.4", "weezl", ] [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unindent" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "visioncortex" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c9a7e6cc136c7c79b4adbd311c46c323a19db7baf446822b5f90f84375934e3" dependencies = [ "bit-vec", "flo_curves", "num-traits", ] [[package]] name = "vtracer" version = "0.6.12" dependencies = [ "clap", "fastrand", "image", "pyo3", "visioncortex", ] [[package]] name = "vtracer-webapp" version = "0.4.0" dependencies = [ "cfg-if 0.1.10", "console_error_panic_hook", "console_log", "serde", "serde_json", "visioncortex", "wasm-bindgen", "web-sys", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if 1.0.4", "once_cell", "rustversion", "serde", "serde_json", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "weezl" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" vtracer-0.6.15/Cargo.toml0000644000000000611046102023000106100ustar [workspace] members = ["cmdapp"] resolver = "2" vtracer-0.6.15/LICENSE0000644000000020421046102023000076660ustar Copyright (c) 2024 TSANG, Hao Fung Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.vtracer-0.6.15/cmdapp/.gitignore0000644000000000211046102023000121100ustar *.svg *.png *.jpgvtracer-0.6.15/cmdapp/Cargo.toml0000644000000012351046102023000120600ustar [package] name = "vtracer" version = "0.6.12" authors = ["Chris Tsang "] edition = "2021" description = "A cmd app to convert images into vector graphics." license = "MIT" homepage = "http://www.visioncortex.org/vtracer" repository = "https://github.com/visioncortex/vtracer/" categories = ["graphics"] keywords = ["svg", "computer-graphics"] readme = "README.md" [dependencies] clap = "2.33.3" image = "0.23.10" visioncortex = { version = "0.8.8" } fastrand = { version = "2.3" } pyo3 = { version = "0.19.0", optional = true } [features] python-binding = ["pyo3"] wasm = ["fastrand/js"] [lib] name = "vtracer" crate-type = ["rlib", "cdylib"] vtracer-0.6.15/cmdapp/LICENSE0000644000000020421046102023000111320ustar Copyright (c) 2024 TSANG, Hao Fung Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.vtracer-0.6.15/cmdapp/README.md0000644000000075771046102023000114260ustar

VTracer

Raster to Vector Graphics Converter built on top of visioncortex

Article | Demo | Download

Built with 🦀 by The Vision Cortex Research Group
## Introduction visioncortex VTracer is an open source software to convert raster images (like jpg & png) into vector graphics (svg). It can vectorize graphics and photographs and trace the curves to output compact vector files. Comparing to [Potrace](http://potrace.sourceforge.net/) which only accept binarized inputs (Black & White pixmap), VTracer has an image processing pipeline which can handle colored high resolution scans. Comparing to Adobe Illustrator's [Image Trace](https://helpx.adobe.com/illustrator/using/image-trace.html), VTracer's output is much more compact (less shapes) as we adopt a stacking strategy and avoid producing shapes with holes. VTracer is originally designed for processing high resolution scans of historic blueprints up to gigapixels. At the same time, VTracer can also handle low resolution pixel art, simulating `image-rendering: pixelated` for retro game artworks. A technical description of the algorithm is on [visioncortex.org/vtracer-docs](https://www.visioncortex.org/vtracer-docs). ## Cmd App ```sh visioncortex VTracer 0.6.0 A cmd app to convert images into vector graphics. USAGE: vtracer [OPTIONS] --input --output FLAGS: -h, --help Prints help information -V, --version Prints version information OPTIONS: --colormode True color image `color` (default) or Binary image `bw` -p, --color_precision Number of significant bits to use in an RGB channel -c, --corner_threshold Minimum momentary angle (degree) to be considered a corner -f, --filter_speckle Discard patches smaller than X px in size -g, --gradient_step Color difference between gradient layers --hierarchical Hierarchical clustering `stacked` (default) or non-stacked `cutout`. Only applies to color mode. -i, --input Path to input raster image -m, --mode Curver fitting mode `pixel`, `polygon`, `spline` -o, --output Path to output vector graphics --path_precision Number of decimal places to use in path string --preset Use one of the preset configs `bw`, `poster`, `photo` -l, --segment_length Perform iterative subdivide smooth until all segments are shorter than this length -s, --splice_threshold Minimum angle displacement (degree) to splice a spline ``` ### Install You can download pre-built binaries from [Releases](https://github.com/visioncortex/vtracer/releases). You can also install the program from source from [crates.io/vtracer](https://crates.io/crates/vtracer): ```sh cargo install vtracer ``` ### Usage ```sh ./vtracer --input input.jpg --output output.svg ``` ## Rust Library You can install [`vtracer`](https://crates.io/crates/vtracer) as a Rust library. ```sh cargo add vtracer ``` ## Python Library Since `0.6`, [`vtracer`](https://pypi.org/project/vtracer/) is also packaged as Python native extensions, thanks to the awesome [pyo3](https://github.com/PyO3/pyo3) project. ```sh pip install vtracer ``` vtracer-0.6.15/cmdapp/src/config.rs0000644000000114171046102023000125350ustar use std::str::FromStr; use visioncortex::PathSimplifyMode; #[derive(Debug, Clone)] pub enum Preset { Bw, Poster, Photo, } #[derive(Debug, Clone)] pub enum ColorMode { Color, Binary, } #[derive(Debug, Clone)] pub enum Hierarchical { Stacked, Cutout, } /// Converter config #[derive(Debug, Clone)] pub struct Config { pub color_mode: ColorMode, pub hierarchical: Hierarchical, pub filter_speckle: usize, pub color_precision: i32, pub layer_difference: i32, pub mode: PathSimplifyMode, pub corner_threshold: i32, pub length_threshold: f64, pub max_iterations: usize, pub splice_threshold: i32, pub path_precision: Option, } #[derive(Debug, Clone)] pub(crate) struct ConverterConfig { pub color_mode: ColorMode, pub hierarchical: Hierarchical, pub filter_speckle_area: usize, pub color_precision_loss: i32, pub layer_difference: i32, pub mode: PathSimplifyMode, pub corner_threshold: f64, pub length_threshold: f64, pub max_iterations: usize, pub splice_threshold: f64, pub path_precision: Option, } impl Default for Config { fn default() -> Self { Self { color_mode: ColorMode::Color, hierarchical: Hierarchical::Stacked, mode: PathSimplifyMode::Spline, filter_speckle: 4, color_precision: 6, layer_difference: 16, corner_threshold: 60, length_threshold: 4.0, splice_threshold: 45, max_iterations: 10, path_precision: Some(2), } } } impl FromStr for ColorMode { type Err = String; fn from_str(s: &str) -> Result { match s { "color" => Ok(Self::Color), "binary" => Ok(Self::Binary), _ => Err(format!("unknown ColorMode {}", s)), } } } impl FromStr for Hierarchical { type Err = String; fn from_str(s: &str) -> Result { match s { "stacked" => Ok(Self::Stacked), "cutout" => Ok(Self::Cutout), _ => Err(format!("unknown Hierarchical {}", s)), } } } impl FromStr for Preset { type Err = String; fn from_str(s: &str) -> Result { match s { "bw" => Ok(Self::Bw), "poster" => Ok(Self::Poster), "photo" => Ok(Self::Photo), _ => Err(format!("unknown Preset {}", s)), } } } impl Config { pub fn from_preset(preset: Preset) -> Self { match preset { Preset::Bw => Self { color_mode: ColorMode::Binary, hierarchical: Hierarchical::Stacked, filter_speckle: 4, color_precision: 6, layer_difference: 16, mode: PathSimplifyMode::Spline, corner_threshold: 60, length_threshold: 4.0, max_iterations: 10, splice_threshold: 45, path_precision: Some(2), }, Preset::Poster => Self { color_mode: ColorMode::Color, hierarchical: Hierarchical::Stacked, filter_speckle: 4, color_precision: 8, layer_difference: 16, mode: PathSimplifyMode::Spline, corner_threshold: 60, length_threshold: 4.0, max_iterations: 10, splice_threshold: 45, path_precision: Some(2), }, Preset::Photo => Self { color_mode: ColorMode::Color, hierarchical: Hierarchical::Stacked, filter_speckle: 10, color_precision: 8, layer_difference: 48, mode: PathSimplifyMode::Spline, corner_threshold: 180, length_threshold: 4.0, max_iterations: 10, splice_threshold: 45, path_precision: Some(2), }, } } pub(crate) fn into_converter_config(self) -> ConverterConfig { ConverterConfig { color_mode: self.color_mode, hierarchical: self.hierarchical, filter_speckle_area: self.filter_speckle * self.filter_speckle, color_precision_loss: 8 - self.color_precision, layer_difference: self.layer_difference, mode: self.mode, corner_threshold: deg2rad(self.corner_threshold), length_threshold: self.length_threshold, max_iterations: self.max_iterations, splice_threshold: deg2rad(self.splice_threshold), path_precision: self.path_precision, } } } fn deg2rad(deg: i32) -> f64 { deg as f64 / 180.0 * std::f64::consts::PI } vtracer-0.6.15/cmdapp/src/converter.rs0000644000000162611046102023000133010ustar use std::path::Path; use std::{fs::File, io::Write}; use super::config::{ColorMode, Config, ConverterConfig, Hierarchical}; use super::svg::SvgFile; use fastrand::Rng; use visioncortex::color_clusters::{KeyingAction, Runner, RunnerConfig, HIERARCHICAL_MAX}; use visioncortex::{Color, ColorImage, ColorName}; const NUM_UNUSED_COLOR_ITERATIONS: usize = 6; /// The fraction of pixels in the top/bottom rows of the image that need to be transparent before /// the entire image will be keyed. const KEYING_THRESHOLD: f32 = 0.2; /// Convert an in-memory image into an in-memory SVG pub fn convert(img: ColorImage, config: Config) -> Result { let config = config.into_converter_config(); match config.color_mode { ColorMode::Color => color_image_to_svg(img, config), ColorMode::Binary => binary_image_to_svg(img, config), } } /// Convert an image file into svg file pub fn convert_image_to_svg( input_path: &Path, output_path: &Path, config: Config, ) -> Result<(), String> { let img = read_image(input_path)?; let svg = convert(img, config)?; write_svg(svg, output_path) } fn color_exists_in_image(img: &ColorImage, color: Color) -> bool { for y in 0..img.height { for x in 0..img.width { let pixel_color = img.get_pixel(x, y); if pixel_color.r == color.r && pixel_color.g == color.g && pixel_color.b == color.b { return true; } } } false } fn find_unused_color_in_image(img: &ColorImage) -> Result { let special_colors = IntoIterator::into_iter([ Color::new(255, 0, 0), Color::new(0, 255, 0), Color::new(0, 0, 255), Color::new(255, 255, 0), Color::new(0, 255, 255), Color::new(255, 0, 255), ]); let mut rng = Rng::new(); let random_colors = (0..NUM_UNUSED_COLOR_ITERATIONS).map(|_| Color::new(rng.u8(..), rng.u8(..), rng.u8(..))); for color in special_colors.chain(random_colors) { if !color_exists_in_image(img, color) { return Ok(color); } } Err(String::from( "unable to find unused color in image to use as key", )) } fn should_key_image(img: &ColorImage) -> bool { if img.width == 0 || img.height == 0 { return false; } // Check for transparency at several scanlines let threshold = ((img.width * 2) as f32 * KEYING_THRESHOLD) as usize; let mut num_transparent_pixels = 0; let y_positions = [ 0, img.height / 4, img.height / 2, 3 * img.height / 4, img.height - 1, ]; for y in y_positions { for x in 0..img.width { if img.get_pixel(x, y).a == 0 { num_transparent_pixels += 1; } if num_transparent_pixels >= threshold { return true; } } } false } fn color_image_to_svg(mut img: ColorImage, config: ConverterConfig) -> Result { let width = img.width; let height = img.height; let key_color = if should_key_image(&img) { 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 { // The default color is all zeroes, which is treated by visioncortex as a special value meaning no keying will be applied. Color::default() }; let runner = Runner::new( RunnerConfig { diagonal: config.layer_difference == 0, hierarchical: HIERARCHICAL_MAX, batch_size: 25600, good_min_area: config.filter_speckle_area, good_max_area: (width * height), is_same_color_a: config.color_precision_loss, is_same_color_b: 1, deepen_diff: config.layer_difference, hollow_neighbours: 1, key_color, keying_action: if matches!(config.hierarchical, Hierarchical::Cutout) { KeyingAction::Keep } else { KeyingAction::Discard }, }, img, ); let mut clusters = runner.run(); match config.hierarchical { Hierarchical::Stacked => {} Hierarchical::Cutout => { let view = clusters.view(); let image = view.to_color_image(); let runner = Runner::new( 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: KeyingAction::Discard, }, image, ); clusters = runner.run(); } } let view = clusters.view(); let mut svg = SvgFile::new(width, height, config.path_precision); for &cluster_index in view.clusters_output.iter().rev() { let cluster = view.get_cluster(cluster_index); let paths = cluster.to_compound_path( &view, false, config.mode, config.corner_threshold, config.length_threshold, config.max_iterations, config.splice_threshold, ); svg.add_path(paths, cluster.residue_color()); } Ok(svg) } fn binary_image_to_svg(img: ColorImage, config: ConverterConfig) -> Result { let img = img.to_binary_image(|x| x.r < 128); let width = img.width; let height = img.height; let clusters = img.to_clusters(false); let mut svg = SvgFile::new(width, height, config.path_precision); for i in 0..clusters.len() { let cluster = clusters.get_cluster(i); if cluster.size() >= config.filter_speckle_area { let paths = cluster.to_compound_path( config.mode, config.corner_threshold, config.length_threshold, config.max_iterations, config.splice_threshold, ); svg.add_path(paths, Color::color(&ColorName::Black)); } } Ok(svg) } fn read_image(input_path: &Path) -> Result { let img = image::open(input_path); let img = match img { Ok(file) => file.to_rgba8(), Err(_) => return Err(String::from("No image file found at specified input path")), }; let (width, height) = (img.width() as usize, img.height() as usize); let img = ColorImage { pixels: img.as_raw().to_vec(), width, height, }; Ok(img) } fn write_svg(svg: SvgFile, output_path: &Path) -> Result<(), String> { let out_file = File::create(output_path); let mut out_file = match out_file { Ok(file) => file, Err(_) => return Err(String::from("Cannot create output file.")), }; write!(&mut out_file, "{}", svg).expect("failed to write file."); Ok(()) } vtracer-0.6.15/cmdapp/src/lib.rs0000644000000012531046102023000120330ustar // Copyright 2023 Tsang Hao Fung. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. mod config; mod converter; #[cfg(feature = "python-binding")] mod python; mod svg; pub use config::*; pub use converter::*; #[cfg(feature = "python-binding")] pub use python::*; pub use svg::*; pub use visioncortex::ColorImage; vtracer-0.6.15/cmdapp/src/main.rs0000644000000217271046102023000122210ustar mod config; mod converter; mod svg; use clap::{App, Arg}; use config::{ColorMode, Config, Hierarchical, Preset}; use std::path::PathBuf; use std::str::FromStr; use visioncortex::PathSimplifyMode; fn path_simplify_mode_from_str(s: &str) -> PathSimplifyMode { match s { "polygon" => PathSimplifyMode::Polygon, "spline" => PathSimplifyMode::Spline, "none" => PathSimplifyMode::None, _ => panic!("unknown PathSimplifyMode {}", s), } } pub fn config_from_args() -> (PathBuf, PathBuf, Config) { let app = App::new("visioncortex VTracer ".to_owned() + env!("CARGO_PKG_VERSION")) .about("A cmd app to convert images into vector graphics."); let app = app.arg( Arg::with_name("input") .long("input") .short("i") .takes_value(true) .help("Path to input raster image") .required(true), ); let app = app.arg( Arg::with_name("output") .long("output") .short("o") .takes_value(true) .help("Path to output vector graphics") .required(true), ); let app = app.arg( Arg::with_name("color_mode") .long("colormode") .takes_value(true) .help("True color image `color` (default) or Binary image `bw`"), ); let app = app.arg( Arg::with_name("hierarchical") .long("hierarchical") .takes_value(true) .help( "Hierarchical clustering `stacked` (default) or non-stacked `cutout`. \ Only applies to color mode. ", ), ); let app = app.arg( Arg::with_name("preset") .long("preset") .takes_value(true) .help("Use one of the preset configs `bw`, `poster`, `photo`"), ); let app = app.arg( Arg::with_name("filter_speckle") .long("filter_speckle") .short("f") .takes_value(true) .help("Discard patches smaller than X px in size"), ); let app = app.arg( Arg::with_name("color_precision") .long("color_precision") .short("p") .takes_value(true) .help("Number of significant bits to use in an RGB channel"), ); let app = app.arg( Arg::with_name("gradient_step") .long("gradient_step") .short("g") .takes_value(true) .help("Color difference between gradient layers"), ); let app = app.arg( Arg::with_name("corner_threshold") .long("corner_threshold") .short("c") .takes_value(true) .help("Minimum momentary angle (degree) to be considered a corner"), ); let app = app.arg(Arg::with_name("segment_length") .long("segment_length") .short("l") .takes_value(true) .help("Perform iterative subdivide smooth until all segments are shorter than this length")); let app = app.arg( Arg::with_name("splice_threshold") .long("splice_threshold") .short("s") .takes_value(true) .help("Minimum angle displacement (degree) to splice a spline"), ); let app = app.arg( Arg::with_name("mode") .long("mode") .short("m") .takes_value(true) .help("Curver fitting mode `pixel`, `polygon`, `spline`"), ); let app = app.arg( Arg::with_name("path_precision") .long("path_precision") .takes_value(true) .help("Number of decimal places to use in path string"), ); // Extract matches let matches = app.get_matches(); let mut config = Config::default(); let input_path = matches .value_of("input") .expect("Input path is required, please specify it by --input or -i."); let output_path = matches .value_of("output") .expect("Output path is required, please specify it by --output or -o."); let input_path = PathBuf::from(input_path); let output_path = PathBuf::from(output_path); if let Some(value) = matches.value_of("preset") { config = Config::from_preset(Preset::from_str(value).unwrap()); } if let Some(value) = matches.value_of("color_mode") { config.color_mode = ColorMode::from_str(if value.trim() == "bw" || value.trim() == "BW" { "binary" } else { "color" }) .unwrap() } if let Some(value) = matches.value_of("hierarchical") { config.hierarchical = Hierarchical::from_str(value).unwrap() } if let Some(value) = matches.value_of("mode") { let value = value.trim(); config.mode = path_simplify_mode_from_str(if value == "pixel" { "none" } else if value == "polygon" { "polygon" } else if value == "spline" { "spline" } else { panic!("Parser Error: Curve fitting mode is invalid: {}", value); }); } if let Some(value) = matches.value_of("filter_speckle") { if value.trim().parse::().is_ok() { // is numeric let value = value.trim().parse::().unwrap(); if value > 16 { panic!("Out of Range Error: Filter speckle is invalid at {}. It must be within [0,16].", value); } config.filter_speckle = value; } else { panic!( "Parser Error: Filter speckle is not a positive integer: {}.", value ); } } if let Some(value) = matches.value_of("color_precision") { if value.trim().parse::().is_ok() { // is numeric let value = value.trim().parse::().unwrap(); if value < 1 || value > 8 { panic!("Out of Range Error: Color precision is invalid at {}. It must be within [1,8].", value); } config.color_precision = value; } else { panic!( "Parser Error: Color precision is not an integer: {}.", value ); } } if let Some(value) = matches.value_of("gradient_step") { if value.trim().parse::().is_ok() { // is numeric let value = value.trim().parse::().unwrap(); if value < 0 || value > 255 { panic!("Out of Range Error: Gradient step is invalid at {}. It must be within [0,255].", value); } config.layer_difference = value; } else { panic!("Parser Error: Gradient step is not an integer: {}.", value); } } if let Some(value) = matches.value_of("corner_threshold") { if value.trim().parse::().is_ok() { // is numeric let value = value.trim().parse::().unwrap(); if value < 0 || value > 180 { panic!("Out of Range Error: Corner threshold is invalid at {}. It must be within [0,180].", value); } config.corner_threshold = value } else { panic!("Parser Error: Corner threshold is not numeric: {}.", value); } } if let Some(value) = matches.value_of("segment_length") { if value.trim().parse::().is_ok() { // is numeric let value = value.trim().parse::().unwrap(); if value < 3.5 || value > 10.0 { panic!("Out of Range Error: Segment length is invalid at {}. It must be within [3.5,10].", value); } config.length_threshold = value; } else { panic!("Parser Error: Segment length is not numeric: {}.", value); } } if let Some(value) = matches.value_of("splice_threshold") { if value.trim().parse::().is_ok() { // is numeric let value = value.trim().parse::().unwrap(); if value < 0 || value > 180 { panic!("Out of Range Error: Segment length is invalid at {}. It must be within [0,180].", value); } config.splice_threshold = value; } else { panic!("Parser Error: Segment length is not numeric: {}.", value); } } if let Some(value) = matches.value_of("path_precision") { if value.trim().parse::().is_ok() { // is numeric let value = value.trim().parse::().ok(); config.path_precision = value; } else { panic!( "Parser Error: Path precision is not an unsigned integer: {}.", value ); } } (input_path, output_path, config) } fn main() { let (input_path, output_path, config) = config_from_args(); let result = converter::convert_image_to_svg(&input_path, &output_path, config); match result { Ok(()) => { println!("Conversion successful."); } Err(msg) => { panic!("Conversion failed with error message: {}", msg); } } } vtracer-0.6.15/cmdapp/src/python.rs0000644000000166221046102023000126140ustar use crate::*; use image::{io::Reader, ImageFormat}; use pyo3::{exceptions::PyException, prelude::*}; use std::io::{BufReader, Cursor}; use std::path::PathBuf; use visioncortex::PathSimplifyMode; /// Python binding #[pyfunction] fn convert_image_to_svg_py( image_path: &str, out_path: &str, colormode: Option<&str>, // "color" or "binary" hierarchical: Option<&str>, // "stacked" or "cutout" mode: Option<&str>, // "polygon", "spline", "none" filter_speckle: Option, // default: 4 color_precision: Option, // default: 6 layer_difference: Option, // default: 16 corner_threshold: Option, // default: 60 length_threshold: Option, // in [3.5, 10] default: 4.0 max_iterations: Option, // default: 10 splice_threshold: Option, // default: 45 path_precision: Option, // default: 8 ) -> PyResult<()> { 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_raw_image_to_svg( img_bytes: Vec, img_format: Option<&str>, // Format of the image (e.g. 'jpg', 'png'... A full list of supported formats can be found [here](https://docs.rs/image/latest/image/enum.ImageFormat.html)). If not provided, the image format will be guessed based on its contents. colormode: Option<&str>, // "color" or "binary" hierarchical: Option<&str>, // "stacked" or "cutout" mode: Option<&str>, // "polygon", "spline", "none" filter_speckle: Option, // default: 4 color_precision: Option, // default: 6 layer_difference: Option, // default: 16 corner_threshold: Option, // default: 60 length_threshold: Option, // in [3.5, 10] default: 4.0 max_iterations: Option, // default: 10 splice_threshold: Option, // default: 45 path_precision: Option, // default: 8 ) -> PyResult { 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)) } #[pyfunction] fn convert_pixels_to_svg( rgba_pixels: Vec<(u8, u8, u8, u8)>, size: (usize, usize), colormode: Option<&str>, // "color" or "binary" hierarchical: Option<&str>, // "stacked" or "cutout" mode: Option<&str>, // "polygon", "spline", "none" filter_speckle: Option, // default: 4 color_precision: Option, // default: 6 layer_difference: Option, // default: 16 corner_threshold: Option, // default: 60 length_threshold: Option, // in [3.5, 10] default: 4.0 max_iterations: Option, // default: 10 splice_threshold: Option, // default: 45 path_precision: Option, // default: 8 ) -> PyResult { let expected_pixel_count = size.0 * size.1; if rgba_pixels.len() != expected_pixel_count { return Err(PyException::new_err(format!( "Length of rgba_pixels does not match given image size. Expected {} ({} * {}), got {}. ", expected_pixel_count, size.0, size.1, rgba_pixels.len() ))); } 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 flat_pixels: Vec = vec![]; for (r, g, b, a) in rgba_pixels { flat_pixels.push(r); flat_pixels.push(g); flat_pixels.push(b); flat_pixels.push(a); } let mut img = ColorImage::new(); img.pixels = flat_pixels; (img.width, img.height) = size; 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, color_precision: Option, layer_difference: Option, corner_threshold: Option, length_threshold: Option, max_iterations: Option, splice_threshold: Option, path_precision: Option, ) -> 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") { "color" => ColorMode::Color, "binary" => ColorMode::Binary, _ => ColorMode::Color, }; let hierarchical = match hierarchical.unwrap_or("stacked") { "stacked" => Hierarchical::Stacked, "cutout" => Hierarchical::Cutout, _ => Hierarchical::Stacked, }; let mode = match mode.unwrap_or("spline") { "spline" => PathSimplifyMode::Spline, "polygon" => PathSimplifyMode::Polygon, "none" => PathSimplifyMode::None, _ => PathSimplifyMode::Spline, }; let filter_speckle = filter_speckle.unwrap_or(4); let color_precision = color_precision.unwrap_or(6); let layer_difference = layer_difference.unwrap_or(16); let corner_threshold = corner_threshold.unwrap_or(60); let length_threshold = length_threshold.unwrap_or(4.0); let splice_threshold = splice_threshold.unwrap_or(45); let max_iterations = max_iterations.unwrap_or(10); Config { color_mode, hierarchical, filter_speckle, color_precision, layer_difference, mode, corner_threshold, length_threshold, max_iterations, splice_threshold, path_precision, ..Default::default() } } /// 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_raw_image_to_svg, m)?)?; m.add_function(wrap_pyfunction!(convert_pixels_to_svg, m)?)?; Ok(()) } vtracer-0.6.15/cmdapp/src/svg.rs0000644000000035611046102023000120700ustar use std::fmt; use visioncortex::{Color, CompoundPath, PointF64}; #[derive(Debug, Clone)] pub struct SvgFile { pub paths: Vec, pub width: usize, pub height: usize, pub path_precision: Option, } #[derive(Debug, Clone)] pub struct SvgPath { pub path: CompoundPath, pub color: Color, } impl SvgFile { pub fn new(width: usize, height: usize, path_precision: Option) -> Self { SvgFile { paths: vec![], width, height, path_precision, } } pub fn add_path(&mut self, path: CompoundPath, color: Color) { self.paths.push(SvgPath { path, color }) } } impl fmt::Display for SvgFile { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, r#""#)?; writeln!( f, r#""#, env!("CARGO_PKG_VERSION") )?; writeln!( f, r#""#, self.width, self.height )?; for path in &self.paths { path.fmt_with_precision(f, self.path_precision)?; } writeln!(f, "") } } impl fmt::Display for SvgPath { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_with_precision(f, None) } } impl SvgPath { fn fmt_with_precision(&self, f: &mut fmt::Formatter, precision: Option) -> fmt::Result { let (string, offset) = self .path .to_svg_string(true, PointF64::default(), precision); writeln!( f, "", string, self.color.to_hex_string(), offset.x, offset.y ) } } vtracer-0.6.15/pyproject.toml0000644000000014221046102023000115760ustar [project] name = "vtracer" version = "0.6.15" description = "Python bindings for the Rust Vtracer raster-to-vector library" authors = [ { name = "Chris Tsang", email = "chris.2y3@outlook.com" } ] readme = "vtracer/README.md" requires-python = ">=3.7" license = "MIT" license-files = ["LICENSE"] classifiers = [ "Programming Language :: Rust", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] [dependencies] python = "^3.7" [dev-dependencies] maturin = "^1.2" [build-system] requires = ["maturin>=1.2,<2.0"] build-backend = "maturin" [tool.maturin] features = ["pyo3/extension-module", "python-binding"] compatibility = "manylinux2014" include = ["LICENSE"] manifest-path = "cmdapp/Cargo.toml" vtracer-0.6.15/vtracer/README.md0000644000000072751046102023000116230ustar

VTracer: Python Binding

Raster to Vector Graphics Converter built on top of visioncortex

Article | Demo | Download

Built with 🦀 by The Vision Cortex Research Group
## Introduction visioncortex VTracer is an open source software to convert raster images (like jpg & png) into vector graphics (svg). It can vectorize graphics and photographs and trace the curves to output compact vector files. Comparing to [Potrace](http://potrace.sourceforge.net/) which only accept binarized inputs (Black & White pixmap), VTracer has an image processing pipeline which can handle colored high resolution scans. Comparing to Adobe Illustrator's [Image Trace](https://helpx.adobe.com/illustrator/using/image-trace.html), VTracer's output is much more compact (less shapes) as we adopt a stacking strategy and avoid producing shapes with holes. VTracer is originally designed for processing high resolution scans of historic blueprints up to gigapixels. At the same time, VTracer can also handle low resolution pixel art, simulating `image-rendering: pixelated` for retro game artworks. A technical description of the algorithm is on [visioncortex.org/vtracer-docs](//www.visioncortex.org/vtracer-docs). ## Install (Python) ```shell pip install vtracer ``` ### Usage (Python) ```python import vtracer input_path = "/path/to/some_file.jpg" output_path = "/path/to/some_file.vtracer.jpg" # Minimal example: use all default values, generate a multicolor SVG vtracer.convert_image_to_svg_py(inp, out) # Single-color example. Good for line art, and much faster than full color: vtracer.convert_image_to_svg_py(inp, out, colormode='binary') # Convert from raw image bytes input_img_bytes: bytes = get_bytes() # e.g. reading bytes from a file or a HTTP request body svg_str: str = vtracer.convert_raw_image_to_svg(input_img_bytes, img_format='jpg') # Convert from RGBA image pixels from PIL import Image img = Image.open(input_path).convert('RGBA') pixels: list[tuple[int, int, int, int]] = list(img.getdata()) svg_str: str = vtracer.convert_pixels_to_svg(pixels, img.size) # All the bells & whistles, also applicable to convert_raw_image_to_svg and convert_pixels_to_svg. vtracer.convert_image_to_svg_py(inp, out, colormode = 'color', # ["color"] or "binary" hierarchical = 'stacked', # ["stacked"] or "cutout" mode = 'spline', # ["spline"] "polygon", or "none" filter_speckle = 4, # default: 4 color_precision = 6, # default: 6 layer_difference = 16, # default: 16 corner_threshold = 60, # default: 60 length_threshold = 4.0, # in [3.5, 10] default: 4.0 max_iterations = 10, # default: 10 splice_threshold = 45, # default: 45 path_precision = 3 # default: 8 ) ``` ## Rust Library The (Rust) library can be found on [crates.io/vtracer](//crates.io/crates/vtracer) and [crates.io/vtracer-webapp](//crates.io/crates/vtracer-webapp). vtracer-0.6.15/vtracer/__init__.py0000644000000001661046102023000124450ustar from .vtracer import (convert_image_to_svg_py, convert_pixels_to_svg, convert_raw_image_to_svg) vtracer-0.6.15/vtracer/vtracer.pyi0000644000000070101046102023000125200ustar from typing import Optional def convert_image_to_svg_py(image_path: str, out_path: str, 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 ) -> None: ... def convert_raw_image_to_svg(img_bytes: bytes, img_format: Optional[str] = None, # Format of the image (e.g. 'jpg', 'png'... A full list of supported formats can be found [here](https://docs.rs/image/latest/image/enum.ImageFormat.html)). 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: ... def convert_pixels_to_svg(rgba_pixels: list[tuple[int, int, int, int]], size: tuple[int, int], 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: ...