extern crate dft;
extern crate image;
use self::image::FilterType;
use cache::Cache;
use std::f64;
use std::fmt;
use std::path::Path;
mod ahash;
mod dhash;
mod phash;
const FLOAT_PRECISION_MAX_1: f64 = f64::MAX / 10_f64;
const FLOAT_PRECISION_MIN_1: f64 = f64::MIN / 10_f64;
const FLOAT_PRECISION_MAX_2: f64 = f64::MAX / 100_f64;
const FLOAT_PRECISION_MIN_2: f64 = f64::MIN / 100_f64;
const FLOAT_PRECISION_MAX_3: f64 = f64::MAX / 1000_f64;
const FLOAT_PRECISION_MIN_3: f64 = f64::MIN / 1000_f64;
const FLOAT_PRECISION_MAX_4: f64 = f64::MAX / 10000_f64;
const FLOAT_PRECISION_MIN_4: f64 = f64::MIN / 10000_f64;
const FLOAT_PRECISION_MAX_5: f64 = f64::MAX / 100000_f64;
const FLOAT_PRECISION_MIN_5: f64 = f64::MIN / 100000_f64;
const HAMMING_DISTANCE_SIMILARITY_LIMIT: u64 = 5u64;
pub struct PreparedImage<'a> {
orig_path: &'a str,
image: Option<image::DynamicImage>,
}
pub struct PerceptualHashes<'a> {
pub orig_path: &'a str,
pub ahash: u64,
pub dhash: u64,
pub phash: u64,
}
impl<'a> PerceptualHashes<'a> {
pub fn similar(&self, other: &'a PerceptualHashes<'a>) -> bool {
if self.orig_path != other.orig_path
&& calculate_hamming_distance(self.ahash, other.ahash)
<= HAMMING_DISTANCE_SIMILARITY_LIMIT
&& calculate_hamming_distance(self.dhash, other.dhash)
<= HAMMING_DISTANCE_SIMILARITY_LIMIT
&& calculate_hamming_distance(self.phash, other.phash)
<= HAMMING_DISTANCE_SIMILARITY_LIMIT
{
true
} else {
false
}
}
}
#[allow(dead_code)]
pub enum Precision {
Low,
Medium,
High,
}
impl Precision {
fn get_size(&self) -> u32 {
match *self {
Precision::Low => 4,
Precision::Medium => 8,
Precision::High => 16,
}
}
}
pub enum HashType {
AHash,
DHash,
PHash,
}
impl fmt::Display for HashType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
HashType::AHash => write!(f, "AHash"),
HashType::DHash => write!(f, "DHash"),
HashType::PHash => write!(f, "PHash"),
}
}
}
pub trait PerceptualHash {
fn get_hash(&self, cache: &Option<Cache>) -> u64;
}
pub fn prepare_image<'a>(
path: &'a Path,
hash_type: &HashType,
precision: &Precision,
cache: &Option<Cache>,
) -> PreparedImage<'a> {
let image_path = path.to_str().unwrap();
let size: u32 = match *hash_type {
HashType::PHash => precision.get_size() * 4,
_ => precision.get_size(),
};
match *cache {
Some(ref c) => {
match c.get_image_from_cache(&path, size) {
Some(image) => PreparedImage {
orig_path: &*image_path,
image: Some(image),
},
None => {
let processed_image = process_image(&image_path, size);
match processed_image.image {
Some(ref image) => {
match c.put_image_in_cache(&path, size, &image) {
Ok(_) => {}
Err(e) => println!("Unable to store image in cache. {}", e),
};
}
None => {}
};
processed_image
}
}
}
None => process_image(&image_path, size),
}
}
fn process_image<'a>(image_path: &'a str, size: u32) -> PreparedImage<'a> {
let image = match image::open(Path::new(image_path)) {
Ok(image) => {
let small_image = image.resize_exact(size, size, FilterType::Lanczos3);
Some(small_image.grayscale())
}
Err(e) => {
println!("Error Processing Image [{}]: {} ", image_path, e);
None
}
};
PreparedImage {
orig_path: &*image_path,
image,
}
}
pub fn get_perceptual_hash<'a>(
path: &'a Path,
precision: &Precision,
hash_type: &HashType,
cache: &Option<Cache>,
) -> u64 {
match *hash_type {
HashType::AHash => ahash::AHash::new(&path, &precision, &cache).get_hash(&cache),
HashType::DHash => dhash::DHash::new(&path, &precision, &cache).get_hash(&cache),
HashType::PHash => phash::PHash::new(&path, &precision, &cache).get_hash(&cache),
}
}
pub fn get_perceptual_hashes<'a>(
path: &'a Path,
precision: &Precision,
cache: &Option<Cache>,
) -> PerceptualHashes<'a> {
let image_path = path.to_str().unwrap();
let ahash = ahash::AHash::new(&path, &precision, &cache).get_hash(&cache);
let dhash = dhash::DHash::new(&path, &precision, &cache).get_hash(&cache);
let phash = phash::PHash::new(&path, &precision, &cache).get_hash(&cache);
PerceptualHashes {
orig_path: &*image_path,
ahash: ahash,
dhash: dhash,
phash: phash,
}
}
pub fn calculate_hamming_distance(hash1: u64, hash2: u64) -> u64 {
(hash1 ^ hash2).count_ones() as u64
}