timelapse_merger/src/main.rs

250 lines
9.9 KiB
Rust
Raw Normal View History

2020-06-10 17:43:01 +00:00
use std::fs;
use std::fmt;
use std::env;
use std::path;
use std::process;
use std::cmp::min;
use std::cmp::max;
use image;
fn print_usage(){
let args: Vec<String> = env::args().collect();
eprintln!("Usage:");
eprintln!(" {} <image_out> <folder_in> <swipe_factor> <effect> <image_samples>", args[0]);
eprintln!(" swipe_factor {{1~-1}}");
eprintln!(" effect {{harsh|dissolve|dissolve{{2|3|4|5|6|7|8}}}}");
eprintln!(" image_samples {{1~...}} 1=all");
}
enum EffectEnum {
HARSH,
DISSOLVE,
DISSOLVE2,
DISSOLVE3,
DISSOLVE4,
DISSOLVE5,
DISSOLVE6,
DISSOLVE7,
DISSOLVE8,
}
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"),
EffectEnum::DISSOLVE2 => write!(f, "DISSOLVE2"),
EffectEnum::DISSOLVE3 => write!(f, "DISSOLVE3"),
EffectEnum::DISSOLVE4 => write!(f, "DISSOLVE4"),
EffectEnum::DISSOLVE5 => write!(f, "DISSOLVE5"),
EffectEnum::DISSOLVE6 => write!(f, "DISSOLVE6"),
EffectEnum::DISSOLVE7 => write!(f, "DISSOLVE7"),
EffectEnum::DISSOLVE8 => write!(f, "DISSOLVE8"),
}
}
}
type EffectFn = fn(f64, f64, u8, u8) -> u8;
fn main() {
let args: Vec<String> = env::args().collect();
match args.len() {
6 => {
let image_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", folder_in);
eprintln!("error: <folder_in> argument should exist");
print_usage();
process::exit(1);
};
let swipe_factor_untreated: f64 = match args[3].parse() {
Ok(n) => n,
Err(_) => {
eprintln!("error: <swipe_factor> argument not a number");
print_usage();
process::exit(1);
},
};
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 effect: EffectEnum = match args[4].as_str() {
"harsh" => EffectEnum::HARSH,
"dissolve" => EffectEnum::DISSOLVE,
"dissolve2" => EffectEnum::DISSOLVE2,
"dissolve3" => EffectEnum::DISSOLVE3,
"dissolve4" => EffectEnum::DISSOLVE4,
"dissolve5" => EffectEnum::DISSOLVE5,
"dissolve6" => EffectEnum::DISSOLVE6,
"dissolve7" => EffectEnum::DISSOLVE7,
"dissolve8" => EffectEnum::DISSOLVE8,
_ => {
eprintln!("error: <effect> argument not supported");
print_usage();
process::exit(2);
},
};
let mut reversed_image_order = false;
let image_samples: i64 = match args[5].parse() {
Ok(n) => {
if n == 0 {
eprintln!("warning: <image_samples> was increased to 1");
1
} else if n < 0 {
reversed_image_order = true;
(n as i64).abs()
} else {
n
}
},
Err(_) => {
eprintln!("error: <image_samples> argument not a number");
print_usage();
process::exit(3);
},
};
// println!("image_out: {:?}", image_out);
// println!("folder_in: {:?}", folder_in);
// println!("swipe_factor: {}", swipe_factor);
// println!("effect: {}", effect);
// println!("image_samples: {:?}", image_samples);
process::exit(main_parsed(image_out, folder_in, swipe_factor, effect, image_samples, reversed_image_order));
}
_ => {
print_usage();
process::exit(1);
}
}
}
fn main_parsed(image_out: &path::Path, folder_in: &path::Path, swipe_factor: f64, effect: EffectEnum, mut image_samples: i64, reversed_image_order: bool) -> i32 {
let mut folder_in_files_: Vec<path::PathBuf> = 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;
}
let sample_spacing: i64 = (folder_in_files.len() as i64)/image_samples;
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 {
return 4;
}
let dimension: (u32, u32) = dimensions[0];
let output_canvas: image::RgbaImage = image::RgbaImage::new(dimension.0, dimension.1);
let mut sample_images: Vec<image::RgbaImage> = samples.iter().map(|path| image::open(path).unwrap().to_rgba()).collect();
if reversed_image_order {
sample_images.reverse();
}
let effect_fn: EffectFn = match effect {
EffectEnum::DISSOLVE => interpolate_linear,
EffectEnum::DISSOLVE2 => interpolate_billinear,
EffectEnum::DISSOLVE3 => interpolate_cubic,
EffectEnum::DISSOLVE4 => interpolate_pow4,
EffectEnum::DISSOLVE5 => interpolate_pow5,
EffectEnum::DISSOLVE6 => interpolate_pow6,
EffectEnum::DISSOLVE7 => interpolate_pow7,
EffectEnum::DISSOLVE8 => interpolate_pow8,
_ => interpolate_nearest
};
return fill_image(image_out, output_canvas, sample_images, swipe_factor, effect_fn);
}
fn fill_image(image_out: &path::Path, mut output_canvas: image::RgbaImage, sample_images: Vec<image::RgbaImage>, swipe_factor: f64, effect: EffectFn) -> i32 {
let max_dimens: (u32, u32) = output_canvas.dimensions();
let y_pixels_spacing: i64 = (max_dimens.1 as i64)/((sample_images.len()-1) as i64);
for y in 0..max_dimens.1 {
for x in 0..max_dimens.0 {
let spxs: Vec<&image::Rgba<u8>> = sample_images.iter().map(|si| si.get_pixel(x, y)).collect();
let y_displacement: i64 = ((((x as i64) - ((max_dimens.0/2) as i64)) as f64) * swipe_factor).round() as i64;
let y_displaced__: i64 = y as i64 + y_displacement;
// let y_displaced_: i64 = min(max(y_displaced__, 0), (max_dimens.1 as i64)-1);
// let y_displaced: u32 = y_displaced_ as u32;
// let gradient_y: i64 = ((((x as i32)-((max_dimens.1 as i32)/2)) as f64) * y_displacement_by_x).round() as i64;
let positioning__: f64 = (y_displaced__ as f64)/(y_pixels_spacing as f64);
let positioning_max_: i64 = positioning__.ceil() as i64;
let positioning_min_: i64 = positioning__.floor() as i64;
let positioning_min: u32 = min((sample_images.len()-1) as u32, max(positioning_min_, 0) as u32);
let positioning_max: u32 = min((sample_images.len()-1) as u32, max(positioning_max_, 0) as u32);
output_canvas.get_pixel_mut(x, y)[0] = if ((y_displaced__ as i64)%y_pixels_spacing) == 0 {255} else {0};
output_canvas.get_pixel_mut(x, y)[3] = 255;
let spxu = spxs[positioning_max as usize];
let spxl = spxs[positioning_min as usize];
for i in 0..4 {
output_canvas.get_pixel_mut(x, y)[i] = effect(
(positioning_max_ as f64 - positioning__) as f64,
(positioning__ - positioning_min_ as f64) as f64,
spxl[i],
spxu[i],
)
}
// print!("| {:?} {:?}", (x, y), (positioning_min_, positioning_max_));
// println!("{} {} {:?} {:?}", x, y, px, spxs);
}
}
output_canvas.save_with_format(image_out, image::ImageFormat::Png).unwrap();
return 0;
}
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 interpolate_linear(d1: f64, d2: f64, v1: u8, v2: u8) -> u8 {
return interpolate_power(d1, d2, v1, v2, 1.0);
}
fn interpolate_billinear(d1: f64, d2: f64, v1: u8, v2: u8) -> u8 {
return interpolate_power(d1, d2, v1, v2, 2.0);
}
fn interpolate_cubic(d1: f64, d2: f64, v1: u8, v2: u8) -> u8 {
return interpolate_power(d1, d2, v1, v2, 3.0);
}
fn interpolate_pow4(d1: f64, d2: f64, v1: u8, v2: u8) -> u8 {
return interpolate_power(d1, d2, v1, v2, 4.0);
}
fn interpolate_pow5(d1: f64, d2: f64, v1: u8, v2: u8) -> u8 {
return interpolate_power(d1, d2, v1, v2, 5.0);
}
fn interpolate_pow6(d1: f64, d2: f64, v1: u8, v2: u8) -> u8 {
return interpolate_power(d1, d2, v1, v2, 6.0);
}
fn interpolate_pow7(d1: f64, d2: f64, v1: u8, v2: u8) -> u8 {
return interpolate_power(d1, d2, v1, v2, 7.0);
}
fn interpolate_pow8(d1: f64, d2: f64, v1: u8, v2: u8) -> u8 {
return interpolate_power(d1, d2, v1, v2, 8.0);
}