use image; use std::cmp::max; use std::cmp::min; use std::env; use std::fmt; use std::fs; use std::path; use std::process; fn print_usage() { let args: Vec = env::args().collect(); eprintln!("Usage:"); eprintln!( " {} ", args[0] ); eprintln!(" swipe_factor {{1~-1}}"); eprintln!(" effect {{harsh|dissolve|dissolve:}}"); eprintln!(" image_samples {{1~...}} 1=all"); } #[derive(PartialEq)] enum EffectEnum { HARSH, DISSOLVE, } impl fmt::Display for EffectEnum { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { EffectEnum::HARSH => write!(f, "HARSH"), EffectEnum::DISSOLVE => write!(f, "DISSOLVE"), } } } trait Interpolator { fn interpolate(&self, d1: f64, d2: f64, v1: u8, v2: u8) -> u8; } struct IntegerInterpolator {} struct PowerInterpolator { exponent: f64, } impl Interpolator for IntegerInterpolator { fn interpolate(&self, d1: f64, d2: f64, v1: u8, v2: u8) -> u8 { return interpolate_nearest(d1, d2, v1, v2); } } impl Interpolator for PowerInterpolator { fn interpolate(&self, d1: f64, d2: f64, v1: u8, v2: u8) -> u8 { return interpolate_power(d1, d2, v1, v2, self.exponent); } } impl From<&str> for IntegerInterpolator { fn from(_: &str) -> IntegerInterpolator { IntegerInterpolator {} } } impl From<&str> for PowerInterpolator { fn from(arg: &str) -> PowerInterpolator { let power: f64 = if arg == "" { 1.0 } else { match arg.parse() { Ok(n) => n, Err(_) => { eprintln!("error: \"dissolve:\" argument not a number (error 10: input_should_be_a_float_number)"); process::exit(10); } } }; PowerInterpolator { exponent: power } } } fn interpolate_nearest(d1: f64, d2: f64, v1: u8, v2: u8) -> u8 { if d1 >= d2 { return v1; } else { return v2; } } fn interpolate_power(d1: f64, d2: f64, v1: u8, v2: u8, power: f64) -> u8 { if v1 == v2 { return v1; } let ds: f64 = d1.abs() + d2.abs(); let a1: f64 = (d1.abs() / ds).powf(power); let a2: f64 = (d2.abs() / ds).powf(power); let bs: f64 = a1.abs() + a2.abs(); let b1: f64 = a1.abs() / bs; let b2: f64 = a2.abs() / bs; let px: u8 = ((v1 as f64) * b1 + (v2 as f64) * b2).round() as u8; return px; } fn main() { let args: Vec = env::args().collect(); match args.len() { 6 => { let png_out: &path::Path = path::Path::new(&args[1]); let folder_in: &path::Path = path::Path::new(&args[2]); if !folder_in.exists() { eprintln!( "error: {:?} does not exist (error 2: input_path_should_exist)", folder_in ); eprintln!("error: argument should exist"); print_usage(); process::exit(2); }; let swipe_factor_untreated: f64 = match args[3].parse() { Ok(n) => n, Err(_) => { eprintln!("error: argument not a number (error 3: input_should_be_a_float_number)"); print_usage(); process::exit(3); } }; let swipe_factor: f64 = if swipe_factor_untreated > 1.0 { 1.0 } else if swipe_factor_untreated < -1.0 { -1.0 } else { swipe_factor_untreated }; let arg_effect_raw: &str = args[4].as_str(); let arg_effect_raw_split: (&str, &str) = match arg_effect_raw.find(":") { Some(n) => arg_effect_raw.split_at(n), None => (arg_effect_raw, ""), }; let effect: (EffectEnum, &str) = match arg_effect_raw_split.0 { "harsh" => (EffectEnum::HARSH, &arg_effect_raw_split.1[1..]), "dissolve" => (EffectEnum::DISSOLVE, &arg_effect_raw_split.1[1..]), _ => { eprintln!("error: argument not supported (error 4: input_should_be_already_enumerated)"); print_usage(); process::exit(4); } }; let mut reversed_image_order = false; let image_samples: i64 = match args[5].parse() { Ok(n) => { if n == 0 { eprintln!("warning: was increased to 1 (error 5: input_out_of_range)"); 1 } else if n < 0 { reversed_image_order = true; (n as i64).abs() } else { n } } Err(_) => { eprintln!("error: argument not a number (error 6: input_should_be_an_integer_number)"); print_usage(); process::exit(6); } }; process::exit(main_parsed( png_out, folder_in, swipe_factor, effect, image_samples, reversed_image_order, )); } _ => { print_usage(); process::exit(1); } } } fn main_parsed( png_out: &path::Path, folder_in: &path::Path, swipe_factor: f64, effect: (EffectEnum, &str), mut image_samples: i64, reversed_image_order: bool, ) -> i32 { let mut folder_in_files_: Vec = folder_in .read_dir() .unwrap() .map(|it| it.unwrap().path()) .filter(|it| fs::metadata(it).unwrap().is_file()) .collect(); folder_in_files_.sort(); let folder_in_files: Vec<&path::PathBuf> = folder_in_files_.iter().collect(); if image_samples == 1 { image_samples = folder_in_files.len() as i64; } if folder_in_files.len() == 0 { eprintln!("error: There are no images in specified folder. (error 7: empty_input)"); return 7; } let mut sample_spacing_: i64 = (folder_in_files.len() as i64) / image_samples; if sample_spacing_ == 0 { eprintln!("warning: There are less images available than the requested sample; reducing sample size (error 8: image_samples__lt__files_len)"); image_samples = folder_in_files.len() as i64; sample_spacing_ = 1; } let sample_spacing: i64 = max(1, sample_spacing_); let sample_padding: i64 = ((folder_in_files.len() as i64) - (sample_spacing * image_samples)) / 2; let samples: Vec<&&path::PathBuf> = folder_in_files .iter() .enumerate() .filter(|e| ((e.0 as i64) % sample_spacing) == sample_padding) .map(|e| e.1) .collect(); let mut dimensions: Vec<(u32, u32)> = samples .iter() .map(|sample_path| image::image_dimensions(sample_path).unwrap()) .collect(); dimensions.dedup(); if dimensions.len() != 1 { eprintln!("error: Found images with different sizes (error 9: image_size_consistency)"); return 9; } let dimension: (u32, u32) = dimensions[0]; let output_canvas: image::RgbaImage = image::RgbaImage::new(dimension.0, dimension.1); let mut sample_images: Vec = samples .iter() .map(|path| image::open(path).unwrap().to_rgba()) .collect(); if reversed_image_order { sample_images.reverse(); } let effect_arg: &str = effect.1; let final_func = |effect_fn| { fill_image( png_out, output_canvas, sample_images, swipe_factor, effect_fn, ) }; return match effect.0 { EffectEnum::HARSH => final_func(&IntegerInterpolator::from(effect_arg)), EffectEnum::DISSOLVE => final_func(&PowerInterpolator::from(effect_arg)), }; } fn fill_image( png_out: &path::Path, mut output_canvas: image::RgbaImage, sample_images: Vec, swipe_factor: f64, effect: &dyn Interpolator, ) -> i32 { let max_dimens: (u32, u32) = output_canvas.dimensions(); let dx = max_dimens.0 as i64; let dy = max_dimens.1 as i64; let y_pixels_spacing: i64 = dy / ((sample_images.len() - 1) as i64); for x in 0..max_dimens.0 { let y_displacement: i64 = ((((x as i64) - (dx / 2)) as f64) * swipe_factor).round() as i64; for y in 0..max_dimens.1 { let y_displaced: i64 = y as i64 + y_displacement; let positioning: f64 = (y_displaced as f64) / (y_pixels_spacing as f64); let positioning_max_: usize = positioning.ceil() as usize; let positioning_min_: usize = positioning.floor() as usize; let bounded = |val| limit_bound(val, 0, sample_images.len() - 1); let positioning_min: usize = bounded(positioning_min_); let positioning_max: usize = bounded(positioning_max_); let spxu = sample_images[positioning_max].get_pixel(x, y); let spxl = sample_images[positioning_min].get_pixel(x, y); let this_px = output_canvas.get_pixel_mut(x, y); for i in 0..4 { this_px[i] = effect.interpolate( (positioning_max_ as f64 - positioning) as f64, (positioning - positioning_min_ as f64) as f64, spxl[i], spxu[i], ) } } } output_canvas .save_with_format(png_out, image::ImageFormat::Png) .unwrap(); 0 } fn limit_bound(value: T, lower: T, upper: T) -> T { min(upper, max(value, lower)) }