250 lines
9.9 KiB
Rust
250 lines
9.9 KiB
Rust
|
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);
|
||
|
}
|
||
|
|