298 lines
9.8 KiB
Rust
298 lines
9.8 KiB
Rust
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<String> = env::args().collect();
|
|
eprintln!("Usage:");
|
|
eprintln!(
|
|
" {} <png_out> <folder_in> <swipe_factor> <effect> <image_samples>",
|
|
args[0]
|
|
);
|
|
eprintln!(" swipe_factor {{1~-1}}");
|
|
eprintln!(" effect {{harsh|dissolve|dissolve:<exponent>}}");
|
|
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:<exponent>\" 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<String> = 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: <folder_in> argument should exist");
|
|
print_usage();
|
|
process::exit(2);
|
|
};
|
|
let swipe_factor_untreated: f64 = match args[3].parse() {
|
|
Ok(n) => n,
|
|
Err(_) => {
|
|
eprintln!("error: <swipe_factor> 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: <effect> 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: <image_samples> 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: <image_samples> 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<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;
|
|
}
|
|
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<image::RgbaImage> = 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<image::RgbaImage>,
|
|
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<T: Ord>(value: T, lower: T, upper: T) -> T {
|
|
min(upper, max(value, lower))
|
|
}
|