#![deny(unsafe_code)]
#![warn(
clippy::all,
clippy::await_holding_lock,
clippy::char_lit_as_u8,
clippy::checked_conversions,
clippy::dbg_macro,
clippy::debug_assert_with_mut_call,
clippy::doc_markdown,
clippy::empty_enum,
clippy::enum_glob_use,
clippy::exit,
clippy::expl_impl_clone_on_copy,
clippy::explicit_deref_methods,
clippy::explicit_into_iter_loop,
clippy::fallible_impl_from,
clippy::filter_map_next,
clippy::float_cmp_const,
clippy::fn_params_excessive_bools,
clippy::if_let_mutex,
clippy::implicit_clone,
clippy::imprecise_flops,
clippy::inefficient_to_string,
clippy::invalid_upcast_comparisons,
clippy::large_types_passed_by_value,
clippy::let_unit_value,
clippy::linkedlist,
clippy::lossy_float_literal,
clippy::macro_use_imports,
clippy::manual_ok_or,
clippy::map_err_ignore,
clippy::map_flatten,
clippy::map_unwrap_or,
clippy::match_on_vec_items,
clippy::match_same_arms,
clippy::match_wildcard_for_single_variants,
clippy::mem_forget,
clippy::mismatched_target_os,
clippy::mut_mut,
clippy::mutex_integer,
clippy::needless_borrow,
clippy::needless_continue,
clippy::option_option,
clippy::path_buf_push_overwrite,
clippy::ptr_as_ptr,
clippy::ref_option_ref,
clippy::rest_pat_in_fully_bound_structs,
clippy::same_functions_in_if_condition,
clippy::semicolon_if_nothing_returned,
clippy::string_add_assign,
clippy::string_add,
clippy::string_lit_as_bytes,
clippy::string_to_string,
clippy::todo,
clippy::trait_duplication_in_bounds,
clippy::unimplemented,
clippy::unnested_or_patterns,
clippy::unused_self,
clippy::useless_transmute,
clippy::verbose_file_reads,
clippy::zero_sized_map_values,
future_incompatible,
nonstandard_style,
rust_2018_idioms
)]
#![allow(unsafe_code)]
#[cfg(not(target_arch = "wasm32"))]
mod progress_window;
mod repeat;
use structopt::StructOpt;
use std::path::PathBuf;
use texture_synthesis::{
image::ImageOutputFormat as ImgFmt, load_dynamic_image, ChannelMask, Dims, Error, Example,
ImageSource, SampleMethod, Session,
};
fn parse_size(input: &str) -> Result<Dims, std::num::ParseIntError> {
let mut i = input.splitn(2, 'x');
let width: u32 = i.next().unwrap_or("").parse()?;
let height: u32 = match i.next() {
Some(num) => num.parse()?,
None => width,
};
Ok(Dims { width, height })
}
fn parse_img_fmt(input: &str) -> Result<ImgFmt, String> {
let fmt = match input {
"png" => ImgFmt::Png,
"jpg" => ImgFmt::Jpeg(75),
"bmp" => ImgFmt::Bmp,
other => {
return Err(format!(
"image format `{}` not one of: 'png', 'jpg', 'bmp'",
other
))
}
};
Ok(fmt)
}
fn parse_mask(input: &str) -> Result<ChannelMask, String> {
let mask = match &input.to_lowercase()[..] {
"r" => ChannelMask::R,
"g" => ChannelMask::G,
"b" => ChannelMask::B,
"a" => ChannelMask::A,
mask => {
return Err(format!(
"unknown mask '{}', must be one of 'a', 'r', 'g', 'b'",
mask
))
}
};
Ok(mask)
}
#[derive(StructOpt)]
#[structopt(rename_all = "kebab-case")]
struct Generate {
#[structopt(long, parse(from_os_str))]
target_guide: Option<PathBuf>,
#[structopt(long = "guides", parse(from_os_str))]
example_guides: Vec<PathBuf>,
#[structopt(long = "save-transform")]
save_transform: Option<PathBuf>,
#[structopt(parse(from_os_str))]
examples: Vec<PathBuf>,
}
#[derive(StructOpt)]
struct TransferStyle {
#[structopt(long)]
style: PathBuf,
#[structopt(long)]
guide: PathBuf,
}
#[derive(StructOpt)]
#[structopt(rename_all = "kebab-case")]
struct FlipAndRotate {
#[structopt(parse(from_os_str))]
examples: Vec<PathBuf>,
}
#[derive(StructOpt)]
enum Subcommand {
#[structopt(name = "transfer-style")]
TransferStyle(TransferStyle),
#[structopt(name = "generate")]
Generate(Generate),
#[structopt(name = "flip-and-rotate")]
FlipAndRotate(FlipAndRotate),
#[structopt(name = "repeat")]
Repeat(repeat::Args),
}
#[derive(StructOpt)]
#[structopt(rename_all = "kebab-case")]
struct Tweaks {
#[structopt(long = "k-neighs", default_value = "50")]
k_neighbors: u32,
#[structopt(long, default_value = "50")]
m_rand: u64,
#[structopt(long, default_value = "1.0")]
cauchy: f32,
#[structopt(long = "backtrack-pct", default_value = "0.5")]
backtrack_percentage: f32,
#[structopt(long = "backtrack-stages", default_value = "5")]
backtrack_stages: u32,
#[structopt(long = "window")]
#[cfg(feature = "progress")]
#[cfg_attr(feature = "progress", structopt(long = "window"))]
#[cfg_attr(
feature = "progress",
doc = "Show a window with the current progress of the generation"
)]
show_window: bool,
#[structopt(long)]
#[cfg(not(target_arch = "wasm32"))]
no_progress: bool,
#[structopt(long)]
seed: Option<u64>,
#[structopt(long, default_value = "0.8")]
alpha: f32,
#[structopt(long)]
rand_init: Option<u64>,
#[structopt(long = "tiling")]
enable_tiling: bool,
}
#[derive(StructOpt)]
#[structopt(
name = "texture-synthesis",
about = "Synthesizes images based on example images",
rename_all = "kebab-case"
)]
struct Opt {
#[structopt(long = "sample-masks")]
sample_masks: Vec<String>,
#[structopt(long, parse(from_os_str))]
inpaint: Option<PathBuf>,
#[structopt(long, parse(try_from_str = parse_mask), conflicts_with = "inpaint")]
inpaint_channel: Option<ChannelMask>,
#[structopt(
long,
default_value = "500",
parse(try_from_str = parse_size)
)]
out_size: Dims,
#[structopt(
long,
default_value = "png",
parse(try_from_str = parse_img_fmt)
)]
stdout_fmt: ImgFmt,
#[structopt(long, parse(try_from_str = parse_size))]
in_size: Option<Dims>,
#[structopt(long = "out", short, parse(from_os_str))]
output_path: PathBuf,
#[structopt(long, parse(from_os_str))]
debug_out_dir: Option<PathBuf>,
#[structopt(short = "t", long = "threads")]
max_threads: Option<usize>,
#[structopt(flatten)]
tweaks: Tweaks,
#[structopt(subcommand)]
cmd: Subcommand,
}
fn main() {
if let Err(e) = real_main() {
eprintln!("error: {}", e);
#[allow(clippy::exit)]
std::process::exit(1);
}
}
fn real_main() -> Result<(), Error> {
let args = Opt::from_args();
{
if args.output_path.to_str() != Some("-") {
match args.output_path.extension().and_then(|ext| ext.to_str()) {
Some("png" | "jpg" | "bmp") => {}
Some(other) => return Err(Error::UnsupportedOutputFormat(other.to_owned())),
None => return Err(Error::UnsupportedOutputFormat(String::new())),
}
}
}
let (mut examples, target_guide) = match &args.cmd {
Subcommand::Generate(gen) => {
let mut examples: Vec<_> = gen.examples.iter().map(Example::new).collect();
if !gen.example_guides.is_empty() {
for (ex, guide) in examples.iter_mut().zip(gen.example_guides.iter()) {
ex.with_guide(guide);
}
}
(examples, gen.target_guide.as_ref())
}
Subcommand::FlipAndRotate(fr) => {
let example_imgs = fr
.examples
.iter()
.map(|path| load_dynamic_image(ImageSource::Path(path)))
.collect::<Result<Vec<_>, _>>()?;
let mut transformed: Vec<Example<'_>> = Vec::with_capacity(example_imgs.len() * 7);
for img in &example_imgs {
transformed.push(Example::new(img.fliph()));
transformed.push(Example::new(img.rotate90()));
transformed.push(Example::new(img.fliph().rotate90()));
transformed.push(Example::new(img.rotate180()));
transformed.push(Example::new(img.fliph().rotate180()));
transformed.push(Example::new(img.rotate270()));
transformed.push(Example::new(img.fliph().rotate270()));
}
let mut examples: Vec<_> = example_imgs.into_iter().map(Example::new).collect();
examples.append(&mut transformed);
(examples, None)
}
Subcommand::TransferStyle(ts) => (vec![Example::new(&ts.style)], Some(&ts.guide)),
Subcommand::Repeat(rep) => {
return repeat::cmd(rep, &args);
}
};
if !args.sample_masks.is_empty() {
for (i, mask) in args.sample_masks.iter().enumerate() {
if i == examples.len() {
break;
}
let example = &mut examples[i];
match mask.as_str() {
"ALL" => example.set_sample_method(SampleMethod::All),
"IGNORE" => example.set_sample_method(SampleMethod::Ignore),
path => example.set_sample_method(SampleMethod::Image(ImageSource::from_path(
std::path::Path::new(path),
))),
};
}
}
let mut sb = Session::builder();
match (args.inpaint_channel, &args.inpaint) {
(Some(channel), None) => {
let inpaint_example = examples.remove(0);
sb = sb.inpaint_example_channel(channel, inpaint_example, args.out_size);
}
(None, Some(inpaint)) => {
let mut inpaint_example = examples.remove(0);
if args.sample_masks.is_empty() {
inpaint_example.set_sample_method(inpaint);
}
sb = sb.inpaint_example(inpaint, inpaint_example, args.out_size);
}
(None, None) => {}
(Some(_), Some(_)) => unreachable!("we prevent this combination with 'conflicts_with'"),
}
sb = sb
.add_examples(examples)
.output_size(args.out_size)
.seed(args.tweaks.seed.unwrap_or_default())
.nearest_neighbors(args.tweaks.k_neighbors)
.random_sample_locations(args.tweaks.m_rand)
.cauchy_dispersion(args.tweaks.cauchy)
.backtrack_percent(args.tweaks.backtrack_percentage)
.backtrack_stages(args.tweaks.backtrack_stages)
.guide_alpha(args.tweaks.alpha)
.tiling_mode(args.tweaks.enable_tiling);
if let Some(mt) = args.max_threads {
sb = sb.max_thread_count(mt);
}
if let Some(tg) = target_guide {
sb = sb.load_target_guide(tg);
}
if let Some(rand_init) = args.tweaks.rand_init {
sb = sb.random_init(rand_init);
}
if let Some(insize) = args.in_size {
sb = sb.resize_input(insize);
}
let session = sb.build()?;
#[cfg(not(target_arch = "wasm32"))]
let progress: Option<Box<dyn texture_synthesis::session::GeneratorProgress>> =
if !args.tweaks.no_progress {
let progress = progress_window::ProgressWindow::new();
#[cfg(feature = "progress")]
let progress = {
if args.tweaks.show_window {
progress.with_preview(args.out_size, std::time::Duration::from_millis(100))?
} else {
progress
}
};
Some(Box::new(progress))
} else {
None
};
#[cfg(target_arch = "wasm32")]
let progress = None;
let generated = session.run(progress);
if let Some(ref dir) = args.debug_out_dir {
generated.save_debug(dir)?;
}
if let Subcommand::Generate(gen) = args.cmd {
if let Some(ref st_path) = gen.save_transform {
if let Err(e) = repeat::save_coordinate_transform(&generated, st_path) {
eprintln!(
"unable to save coordinate transform to '{}': {}",
st_path.display(),
e
);
}
}
}
if args.output_path.to_str() == Some("-") {
let out = std::io::stdout();
let mut out = out.lock();
generated.write(&mut out, args.stdout_fmt)?;
} else {
generated.save(&args.output_path)?;
}
Ok(())
}