pub mod gimp;
pub mod web_algorithms;
#[cfg(feature = "labels")]
pub mod labels;
use cfg_if::cfg_if;
use image::{GenericImage, ImageBuffer, Rgba,RgbaImage};
use std::path::PathBuf;
use web_algorithms::{anomylize, blindMK, monochrome};
#[derive(Clone)]
pub enum RevBlind {
Protan,
Deutan,
Tritan,
}
#[derive(Debug, PartialEq)]
pub struct CombineInfo {
total_height: u32,
total_width: u32,
positions: Vec<(u32, u32)>,
}
pub struct Config {
pub combine_output: bool,
#[cfg(feature = "labels")]
pub render_label: bool,
}
pub fn process(input: &PathBuf, config: &Config) -> Result<(), Box<dyn std::error::Error>> {
let path = format!("{}", input.display());
let img = image::open(&input)?.to_rgba();
let (name, extension) = path.split_at(
path.rfind(".")
.expect("path does not contain a detectable extension"),
);
let filters: [(&str, Box<dyn Fn(&Rgba<u8>) -> Rgba<u8>>); 11] = [
("Achromatomaly", Box::new(|p| anomylize(p, monochrome(p)))),
("Achromatopsia", Box::new(|p| monochrome(p))),
(
"Deuteranomaly",
Box::new(|p| anomylize(p, blindMK(p, RevBlind::Deutan))),
),
("Deuteranopia", Box::new(|p| blindMK(p, RevBlind::Deutan))),
(
"DeuteranopiaBVM97",
Box::new(|p| gimp::bvm97(RevBlind::Deutan)(p)),
),
(
"Protanomaly",
Box::new(|p| anomylize(p, blindMK(p, RevBlind::Protan))),
),
("Protanopia", Box::new(|p| blindMK(p, RevBlind::Protan))),
(
"ProtanopiaBVM97",
Box::new(|p| gimp::bvm97(RevBlind::Protan)(p)),
),
(
"Tritanomaly",
Box::new(|p| anomylize(p, blindMK(p, RevBlind::Tritan))),
),
("Tritanopia", Box::new(|p| blindMK(p, RevBlind::Tritan))),
(
"TritanopiaBVM97",
Box::new(|p| gimp::bvm97(RevBlind::Tritan)(p)),
),
];
let mut processed_images = vec![];
if config.combine_output {
let mut buffer = setup_buffer(&img, "Original", config);
buffer.copy_from(&img, 0, 0);
processed_images.push(buffer);
}
for (label, func) in filters.iter() {
let out_filename = format!("{}_{}{}", name, label, extension);
print!("preparing {:?} ... ", out_filename);
flush()?;
let mut buffer = setup_buffer(&img, &label, config);
for (x, y, pixel) in img.enumerate_pixels() {
buffer.put_pixel(x, y, func(pixel));
}
if config.combine_output {
processed_images.push(buffer);
println!("partial image complete");
} else {
print!("saving ... ");
flush()?;
buffer.save(out_filename)?;
println!("done");
}
}
if config.combine_output {
assert!(processed_images.len() > 0, "no partial images found");
let out_filename = format!("{}_combined{}", name, extension);
let max_dimensions = processed_images.iter().fold((0,0), |b, i| b.max(i.dimensions()));
let positions = calculate_combined_positions(max_dimensions, processed_images.len());
print!("Combining {} images ...", processed_images.len());
flush()?;
let mut buffer: ImageBuffer<Rgba<u8>, _> =
ImageBuffer::new(positions.total_width, positions.total_height);
for (img, (x, y)) in processed_images.iter().zip(positions.positions.iter()) {
buffer.copy_from(img, *x, *y);
}
print!("saving {} ... ", out_filename);
flush()?;
buffer.save(out_filename)?;
}
Ok(())
}
cfg_if! {
if #[cfg(feature = "labels")] {
fn setup_buffer(img: &RgbaImage, label: &str, config: &Config) -> RgbaImage {
let mut width = img.width();
let mut height = img.height();
let label = if config.render_label {
let label = labels::render(label);
height += label.height();
width = width.max(label.width());
Some(label)
} else {
None
};
let mut buffer = ImageBuffer::new(width, height);
if let Some(label) = label {
let x = width.saturating_sub(label.width()) / 2;
buffer.copy_from(&label, x, img.height());
}
buffer
}
} else {
fn setup_buffer(img: &RgbaImage, _label: &str, _: &Config) -> RgbaImage {
ImageBuffer::new(img.width(), img.height())
}
}
}
fn flush() -> std::io::Result<()> {
use std::io::Write;
std::io::stdout().flush()
}
fn calculate_combined_positions((width, height): (u32, u32), n_pictures: usize) -> CombineInfo {
let margin = 25;
assert!(n_pictures > 0, "n_pictures must be non-zero");
let sqrt = (n_pictures as f64).sqrt();
let columns = sqrt.ceil() as u32;
let rows = sqrt.floor() as u32;
println!("columns: {}; rows: {};", columns, rows);
let mut positions = vec![];
for y in 0..rows {
for x in 0..columns {
positions.push((
margin + x * (width + margin),
margin + y * (height + margin),
));
}
}
assert_eq!(n_pictures, positions.len());
CombineInfo {
total_width: margin + (margin + width) * columns,
total_height: margin + (margin + height) * rows,
positions,
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
const MARGIN: u32 = 25;
#[test]
fn single() {
let positions = calculate_combined_positions((20, 10), 1);
assert_eq!(positions.total_width, MARGIN + 20 + MARGIN);
assert_eq!(positions.total_height, MARGIN + 10 + MARGIN);
assert_eq!(positions.positions, [(MARGIN, MARGIN)]);
}
#[test]
fn four() {
let positions = calculate_combined_positions((20, 10), 4);
assert_eq!(
CombineInfo {
total_width: MARGIN + 2 * (20 + MARGIN),
total_height: MARGIN + 2 * (10 + MARGIN),
positions: vec![
(MARGIN, MARGIN),
(MARGIN + 20 + MARGIN, MARGIN),
(MARGIN, MARGIN + 10 + MARGIN),
(MARGIN + 20 + MARGIN, MARGIN + 10 + MARGIN),
],
},
positions
);
}
#[test]
fn twelfe() {
let positions = calculate_combined_positions((20, 10), 12);
let row1 = MARGIN + 10 + MARGIN;
let row2 = MARGIN + 2 * (10 + MARGIN);
assert_eq!(
CombineInfo {
total_width: MARGIN + 4 * (20 + MARGIN),
total_height: MARGIN + 3 * (10 + MARGIN),
positions: vec![
(MARGIN + 0 * 45, MARGIN),
(MARGIN + 1 * 45, MARGIN),
(MARGIN + 2 * 45, MARGIN),
(MARGIN + 3 * 45, MARGIN),
(MARGIN + 0 * 45, row1),
(MARGIN + 1 * 45, row1),
(MARGIN + 2 * 45, row1),
(MARGIN + 3 * 45, row1),
(MARGIN + 0 * 45, row2),
(MARGIN + 1 * 45, row2),
(MARGIN + 2 * 45, row2),
(MARGIN + 3 * 45, row2)
],
},
positions
);
}
}