extern crate getopts;
extern crate dssim;
extern crate rayon;
extern crate rgb;
extern crate lodepng;
extern crate imgref;
extern crate bmp;
use std::path::{Path, PathBuf};
use getopts::Options;
use std::fs;
use std::io;
use dssim::*;
use rayon::prelude::*;
use imgref::*;
#[derive(Debug)]
pub struct Config {
pub src_dir: Option<PathBuf>,
pub dest_dir: Option<PathBuf>,
pub diff_dir: Option<PathBuf>,
pub verbose: bool,
pub help: bool,
}
impl Config {
pub fn new(args: &[String]) -> Config {
let mut opts = Options::new();
opts.optopt("s", "srcDir", "set source dir name", "");
opts.optopt("d", "destDir", "set output dir name", "");
opts.optopt("f", "diffDir", "set diff dir name", "");
opts.optflag("v", "verbose", "toogle verbose mode");
opts.optflag("h", "help", "print this help menu");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => panic!(f.to_string()),
};
let verbose = matches.opt_present("v");
let help = matches.opt_present("h");
let src_dir: Option<PathBuf> = match matches.opt_str("s") {
Some(string) => Some(PathBuf::from(string)),
None => None,
};
let dest_dir: Option<PathBuf> = match matches.opt_str("d") {
Some(string) => Some(PathBuf::from(string)),
None => None,
};
let diff_dir: Option<PathBuf> = match matches.opt_str("f") {
Some(string) => Some(PathBuf::from(string)),
None => None,
};
Config {
src_dir,
dest_dir,
diff_dir,
verbose,
help,
}
}
}
pub fn visit_dirs(dir: &PathBuf, config: &Config) -> io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
visit_dirs(&path, &config)?;
} else {
let dest_file_name =
entry.path().to_str().unwrap().replace(
config.src_dir.clone().unwrap().to_str().unwrap(),
config
.dest_dir
.clone()
.unwrap()
.to_str()
.unwrap(),
);
if Path::new(&dest_file_name).exists() {
if entry.path().extension().unwrap() == "bmp" {
let (diff_value, diff_image) = compare_bmp(&entry, &dest_file_name);
print_diff_result(config.verbose, &entry, diff_value);
if diff_value != 0.0 {
let diff_file_name =
get_diff_file_name_and_validate_path(dest_file_name, config);
output_bmp(diff_file_name, diff_image);
if config.verbose {
eprintln!("diff found in file: {:?}", entry.path());
}
}
} else {
let mut attr = dssim::Dssim::new();
let g1 = attr.create_image(&load(entry.path()).unwrap()).unwrap();
let g2 = attr.create_image(&load(&dest_file_name).unwrap()).unwrap();
attr.set_save_ssim_maps(1);
let (ssim_diff_value, ssim_maps) = attr.compare(&g1, g2);
print_diff_result(config.verbose, &entry, ssim_diff_value);
if ssim_diff_value != 0.0 {
let diff_file_name =
get_diff_file_name_and_validate_path(dest_file_name, config);
output_diff_files(diff_file_name, ssim_maps);
}
if config.verbose {
eprintln!("diff found in file: {:?}", entry.path());
}
}
}
}
}
}
Ok(())
}
fn get_diff_file_name_and_validate_path(dest_file_name: String, config: &Config) -> String {
let diff_file_name = dest_file_name.replace(
config.dest_dir.clone().unwrap().to_str().unwrap(),
config.diff_dir.clone().unwrap().to_str().unwrap(),
);
{
let diff_path = Path::new(&diff_file_name);
let diff_path_dir = diff_path.parent().unwrap();
if !diff_path_dir.exists() {
if config.verbose {
println!("creating directory: {:?}", diff_path_dir);
}
create_path(diff_path);
}
}
diff_file_name
}
fn output_bmp(path_name: String, image: Option<bmp::Image>) {
match image {
Some(image) => {
let _ = image.save(&path_name).unwrap_or_else(|e| {
eprintln!("Failed to save diff_file: {}\nError: {}", path_name, e)
});
}
None => (),
}
}
fn print_diff_result<T: std::fmt::Debug>(verbose: bool, entry: &fs::DirEntry, diff_value: T) {
if verbose {
println!(
"compared file: {:?} had diff value of: {:?}",
entry.path(),
diff_value
);
} else {
println!("{:?}", diff_value);
}
}
fn compare_bmp(entry: &fs::DirEntry, dest_file_name: &String) -> (f32, Option<bmp::Image>) {
let src_img = bmp::open(entry.path());
let dest_img = bmp::open(dest_file_name);
let mut diff_value = 0.0;
if src_img.is_ok() && dest_img.is_ok() {
let src_img = src_img.unwrap();
let dest_img = dest_img.unwrap();
let mut diff_image = bmp::Image::new(src_img.get_width(), src_img.get_height());
for (x, y) in src_img.coordinates() {
let dest_pixel = dest_img.get_pixel(x, y);
let src_pixel = src_img.get_pixel(x, y);
let diff_pixel = subtract(&src_pixel, &dest_pixel);
diff_value += interpolate(&diff_pixel);
diff_image.set_pixel(x, y, diff_pixel);
}
return (diff_value, Some(diff_image));
}
(diff_value, None)
}
fn interpolate(p: &bmp::Pixel) -> f32 {
((p.r / 3) + (p.g / 3) + (p.b / 3)) as f32 / 10000000.0
}
fn subtract(p1: &bmp::Pixel, p2: &bmp::Pixel) -> bmp::Pixel {
let r;
let g;
let b;
if p1.r >= p2.r {
r = p1.r - p2.r;
} else {
r = p2.r - p1.r
}
if p1.g >= p2.g {
g = p1.g - p2.g;
} else {
g = p2.g - p1.g
}
if p1.b >= p2.b {
b = p1.b - p2.b;
} else {
b = p2.b - p1.b
}
bmp::Pixel { r, g, b }
}
fn create_path(path: &Path) {
let mut buffer = path.to_path_buf();
if buffer.is_file() {
buffer.pop();
}
create_dir_if_not_there(buffer);
}
fn create_dir_if_not_there(mut buffer: PathBuf) -> PathBuf {
if buffer == Path::new("") {
buffer
} else {
buffer.pop();
let temp_buffer = buffer.clone();
create_dir_if_not_there(temp_buffer);
if !buffer.exists() && buffer != Path::new("") {
fs::create_dir(&buffer).unwrap();
}
buffer
}
}
fn to_byte(i: f32) -> u8 {
if i <= 0.0 {
0
} else if i >= 255.0 / 256.0 {
255
} else {
(i * 256.0) as u8
}
}
fn output_diff_files(path_name: String, ssim_maps: Vec<SsimMap>) {
ssim_maps.par_iter().enumerate().for_each(|(n, map_meta)| {
let avgssim = map_meta.ssim as f32;
let out: Vec<_> = map_meta
.map
.pixels()
.map(|ssim| {
let max = 1_f32 - ssim;
let maxsq = max * max;
rgb::RGBA8 {
r: to_byte(maxsq * 16.0),
g: to_byte(max * 3.0),
b: to_byte(max / ((1_f32 - avgssim) * 4_f32)),
a: 255,
}
})
.collect();
let write_res = lodepng::encode32_file(
format!("{}-{}.png", path_name, n),
&out,
map_meta.map.width(),
map_meta.map.height(),
);
if write_res.is_err() {
eprintln!("Can't write {}: {:?}", path_name, write_res);
std::process::exit(1);
}
});
}
fn load<P: AsRef<Path>>(path: P) -> Result<ImgVec<RGBAPLU>, lodepng::Error> {
let image = lodepng::decode32_file(path.as_ref())?;
Ok(Img::new(
image.buffer.to_rgbaplu(),
image.width,
image.height,
))
}