Add ability to generate SVG image based on image.

This commit is contained in:
Bill Thiede 2020-06-26 09:22:45 -07:00
parent bb496e61a5
commit 9a1837896e
4 changed files with 362 additions and 1 deletions

237
Cargo.lock generated
View File

@ -6,6 +6,32 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "anyhow"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
[[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.0.0"
@ -36,6 +62,21 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "clap"
version = "2.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "color_quant"
version = "1.0.1"
@ -125,6 +166,15 @@ dependencies = [
"lzw",
]
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.14"
@ -268,9 +318,13 @@ dependencies = [
name = "perler"
version = "0.1.0"
dependencies = [
"anyhow",
"image",
"lazy_static",
"log",
"structopt",
"svg",
"thiserror",
]
[[package]]
@ -285,6 +339,50 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "proc-macro-error"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc175e9777c3116627248584e8f8b3e2987405cabe1c0adf7d1dd28f09dc7880"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cc9795ca17eb581285ec44936da7fc2335a3f34f2ddd13118b6f4d515435c50"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn-mid",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.3.1"
@ -322,6 +420,93 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c"
dependencies = [
"clap",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "svg"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b65a64d32a41db2a8081aa03c1ccca26f246ff681add693f8b01307b137da79"
[[package]]
name = "syn"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "syn-mid"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tiff"
version = "0.5.0"
@ -332,3 +517,55 @@ dependencies = [
"lzw",
"miniz_oxide",
]
[[package]]
name = "unicode-segmentation"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
[[package]]
name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
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"

View File

@ -9,6 +9,10 @@ edition = "2018"
[dependencies]
image = "0.23.6"
log = "0.4.8"
svg = "0.8.0"
structopt = "0.3.15"
thiserror = "1.0.20"
anyhow = "1.0.31"
[dev-dependencies]
lazy_static = "1.4.0"

View File

@ -1,5 +1,80 @@
use image::{DynamicImage, GenericImage, GenericImageView};
use log::debug;
use svg::node::element::Group;
use svg::node::element::{Circle, Rectangle};
use svg::node::Node;
use svg::Document;
use thiserror::Error;
const GRID_SIZE: (usize, usize) = (29, 29);
#[derive(Error, Debug)]
pub enum PerlerError {
#[error("source image too big {image_size:?} > {max_size:?}")]
TooBig {
image_size: (usize, usize),
max_size: (usize, usize),
},
}
pub fn svgify(input: &DynamicImage) -> Result<impl Node, PerlerError> {
let dim = input.dimensions();
let (w, h) = (dim.0 as usize, dim.1 as usize);
if w > GRID_SIZE.0 || h > GRID_SIZE.1 {
return Err(PerlerError::TooBig {
image_size: (w, h),
max_size: GRID_SIZE,
});
}
let box_offset = (5, 5);
let mut grp = Group::new();
for y in 0..GRID_SIZE.0 {
for x in 0..GRID_SIZE.1 {
let x_pos = box_offset.0 + 5 + x * 5;
let y_pos = box_offset.1 + 5 + y * 5;
let c = Circle::new()
.set("stroke", "black")
.set("stroke-width", 0.1)
.set("cx", x_pos)
.set("cy", y_pos);
let c = if x < w && y < h {
let p = input.get_pixel(x as u32, y as u32);
let [r, g, b, a] = p.0;
if a == 255 {
let color = format!("#{:02x}{:02x}{:02x}", r, g, b);
c.set("fill", color).set("r", 2.4)
} else {
c.set("fill", "none").set("r", 0.5)
}
} else {
c.set("fill", "none").set("r", 0.5)
};
grp = grp.add(c);
}
}
let board = Rectangle::new()
.set("fill", "none")
.set("width", 150)
.set("height", 150)
.set("stroke", "black")
.set("stroke-width", 0.1)
.set("x", box_offset.0)
.set("y", box_offset.1)
.set("rx", 1)
.set("ry", 1);
Ok(Document::new()
.set("width", "100%")
.set("height", "100%")
.set(
"viewBox",
(0, 0, (3 + GRID_SIZE.0) * 5, (3 + GRID_SIZE.1) * 5),
)
//.set("viewBox", (0, 0, 215.9, 279.4))
.add(board)
.add(grp))
}
pub fn simplify(input: &DynamicImage) -> DynamicImage {
let (w, h) = input.dimensions();

View File

@ -1 +1,46 @@
fn main() {}
use std::fs::File;
use std::io;
use std::io::Write;
use std::path::PathBuf;
use anyhow::Result;
//use image::imageops::colorops::{index_colors, BiLevel};
//use image::math::nq::NeuQuant;
//use image::DynamicImage;
use structopt::StructOpt;
use perler::{simplify, svgify};
/// Convert image to SVG in the style of perler instructions.
#[derive(StructOpt, Debug)]
#[structopt(name = "perler")]
struct Opt {
/// Input image file.
input: PathBuf,
/// Optional output .svg file. Default prints to stdout.
output: Option<PathBuf>,
}
fn main() -> Result<()> {
let opt = Opt::from_args();
let img = image::open(opt.input)?;
/*
let mut img2 = img.to_rgba();
let raw = img.into_rgba().into_raw();
let cmap = NeuQuant::new(1, 256, &raw);
index_colors(&mut img2, &cmap);
let img = DynamicImage::ImageRgba8(img2.into());
*/
let img = simplify(&img);
let out_svg = svgify(&img)?;
let w: Box<dyn Write> = if let Some(path) = opt.output {
Box::new(File::create(path)?)
} else {
Box::new(io::stdout())
};
svg::write(w, &out_svg)?;
Ok(())
}