diff --git a/FFI-tests/ffi_test.py b/FFI-tests/ffi_test.py index 4a61968..73aac08 100755 --- a/FFI-tests/ffi_test.py +++ b/FFI-tests/ffi_test.py @@ -2,7 +2,6 @@ from ctypes import * from sys import exit, platform -import os large_image1_path = "../test_images/sample_01_large.jpg".encode(encoding="utf-8") medium_image1_path = "../test_images/sample_01_medium.jpg".encode(encoding="utf-8") @@ -57,6 +56,7 @@ class PIHashes(Structure): # Setting the ctypes return type references for the foreign functions # returns a pointer to the library that we'll need to pass to all function calls lib.ext_init.restype = c_void_p +lib.ext_init.argtypes = [c_char_p] # Returns a longlong hash, takes a pointer and a string lib.ext_get_ahash.restype = c_ulonglong lib.ext_get_ahash.argtypes = [c_void_p, c_char_p] @@ -64,14 +64,14 @@ lib.ext_get_dhash.restype = c_ulonglong lib.ext_get_dhash.argtypes = [c_void_p, c_char_p] lib.ext_get_phash.restype = c_ulonglong lib.ext_get_phash.argtypes = [c_void_p, c_char_p] -lib.ext_get_phashes.restype = c_void_p -lib.ext_get_phashes.argtypes = [c_void_p, c_char_p] -lib.ext_free_phashes.argtypes = [c_void_p] +lib.ext_get_pihashes.restype = c_void_p +lib.ext_get_pihashes.argtypes = [c_void_p, c_char_p] +lib.ext_free_pihashes.argtypes = [c_void_p] # Takes a pointer and frees the struct at that memory location lib.ext_free.argtypes = [c_void_p] #initialize the library -lib_struct = lib.ext_init("./.hash_cache".encode(encoding="utf-8")) +lib_struct = lib.ext_init("./.hash_cache".encode('utf-8')) #print("Pointer to lib_struct: ", lib_struct) @@ -81,9 +81,9 @@ lib_struct = lib.ext_init("./.hash_cache".encode(encoding="utf-8")) for image in test_images: print("Requesting hashes for: %s"% image) - phashes = lib.ext_get_phashes(lib_struct, image) + phashes = lib.ext_get_pihashes(lib_struct, image) pihashes = PIHashes.from_address(phashes) - lib.ext_free_phashes(phashes) + lib.ext_free_pihashes(phashes) print("ahash: %i"% unsigned64(pihashes.ahash)) print("dhash: %i"% unsigned64(pihashes.dhash)) print("phash: %i"% unsigned64(pihashes.phash)) diff --git a/src/cache.rs b/src/cache.rs index 3858f0d..9b5552d 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -9,7 +9,7 @@ extern crate num; extern crate sha1; use std::default::Default; -use std::fs::{create_dir_all, remove_dir_all, File}; +use std::fs::{create_dir_all, File, remove_dir_all}; use std::io::{Error, ErrorKind, Read, Write}; use std::option::Option; use std::path::Path; @@ -18,9 +18,9 @@ use std::str::FromStr; use super::rustc_serialize::json; +use self::flate2::Compression; use self::flate2::read::ZlibDecoder; use self::flate2::write::ZlibEncoder; -use self::flate2::Compression; use self::image::DynamicImage; use self::sha1::Sha1; @@ -58,26 +58,26 @@ impl PartialEq for CacheMetadata { * Structure to hold implementation of the cache */ #[repr(C)] -pub struct Cache<'a> { - pub cache_dir: &'a str, +pub struct Cache { + pub cache_dir: String, pub use_cache: bool, } -impl<'a> Default for Cache<'a> { - fn default() -> Cache<'a> { +impl Default for Cache { + fn default() -> Cache { Cache { - cache_dir: DEFAULT_CACHE_DIR, + cache_dir: String::from(DEFAULT_CACHE_DIR), use_cache: true, } } } -impl<'a> Cache<'a> { +impl Cache { /** * Create the required directories for the cache */ pub fn init(&self) -> Result<(), Error> { - match create_dir_all(self.cache_dir) { + match create_dir_all(&self.cache_dir) { Ok(_) => { let metadata_path_str = format!("{}/{}", self.cache_dir, CACHE_METADATA_FILE); let metadata_path = Path::new(&metadata_path_str); @@ -97,8 +97,8 @@ impl<'a> Cache<'a> { // If they match, continue if current_metadata != loaded_metadata { // If they don't wipe the cache to start new - match remove_dir_all(self.cache_dir) { - Ok(_) => match create_dir_all(self.cache_dir) { + match remove_dir_all(&self.cache_dir) { + Ok(_) => match create_dir_all(&self.cache_dir) { Ok(_) => (), Err(e) => println!("Error: {}", e), }, @@ -130,14 +130,14 @@ impl<'a> Cache<'a> { * Clean the cache directory completely */ pub fn clean(&self) -> Result<(), Error> { - remove_dir_all(self.cache_dir) + remove_dir_all(&self.cache_dir) } /** * Get the hash of the desired file and return it as a hex string */ pub fn get_file_hash(&self, path: &Path) -> Result { - let mut source = try!(File::open(&path)); + let mut source = File::open(&path)?; let mut buf: Vec = Vec::new(); source.read_to_end(&mut buf)?; let mut sha1 = Sha1::new(); @@ -170,8 +170,11 @@ impl<'a> Cache<'a> { ); let cache_dir_str = format!("{}/image/{}x{}/{}", self.cache_dir, size, size, &sha1[..10]); - // println!("Saving: {}", cache_path_str); - match create_dir_all(cache_dir_str) { + println!("Test"); + println!("{}", DEFAULT_CACHE_DIR); + println!("{}", &self.cache_dir); + // println!("Saving: {}", &cache_path_str); + match create_dir_all(&cache_dir_str) { Ok(_) => { let file_path = Path::new(&cache_path_str); match File::create(file_path) { @@ -185,10 +188,16 @@ impl<'a> Cache<'a> { } } } - Err(e) => return Err(e), + Err(e) => { + println!("Unable to create file {:?}", file_path); + return Err(e); + } } } - Err(e) => return Err(e), + Err(e) => { + println!("Unable to create directory {:?}", &cache_dir_str); + return Err(e); + } } } Err(e) => { @@ -284,7 +293,7 @@ impl<'a> Cache<'a> { let desire_len = row_str.len() - 1; row_str.truncate(desire_len); row_str.push_str("\n"); - try!(compressor.write(&row_str.into_bytes())); + compressor.write(&row_str.into_bytes())?; } let compressed_matrix = match compressor.finish() { Ok(data) => data, @@ -293,8 +302,8 @@ impl<'a> Cache<'a> { return Err(e); } }; - try!(file.write(&compressed_matrix)); - try!(file.flush()); + file.write(&compressed_matrix)?; + file.flush()?; } Err(e) => { return Err(e); @@ -370,20 +379,27 @@ impl<'a> Cache<'a> { } } -#[test] -fn test_get_file_hash() { - let target = "test_images/sample_01_large.jpg"; - let target_path = Path::new(target); - let cache: Cache = Default::default(); - let hash = cache.get_file_hash(&target_path); - match hash { - Ok(v) => { - println!("Hash: {}", v); - assert_eq!(v, String::from("4beb6f2d852b75a313863916a1803ebad13a3196")); - } - Err(e) => { - println!("Error: {:?}", e); - assert!(false); +#[cfg(test)] +mod tests { + use std::path::Path; + + use cache::Cache; + + #[test] + fn test_get_file_hash() { + let target = "test_images/sample_01_large.jpg"; + let target_path = Path::new(target); + let cache: Cache = Default::default(); + let hash = cache.get_file_hash(&target_path); + match hash { + Ok(v) => { + println!("Hash: {}", v); + assert_eq!(v, String::from("4beb6f2d852b75a313863916a1803ebad13a3196")); + } + Err(e) => { + println!("Error: {:?}", e); + assert!(false); + } } } } diff --git a/src/hash/ahash.rs b/src/hash/ahash.rs index e8f8c29..1301d78 100644 --- a/src/hash/ahash.rs +++ b/src/hash/ahash.rs @@ -6,24 +6,23 @@ use std::path::Path; use cache::Cache; -use super::prepare_image; use super::{HashType, PerceptualHash, Precision, PreparedImage}; - use super::image::GenericImageView; +use super::prepare_image; -pub struct AHash<'a> { - prepared_image: Box>, +pub struct AHash { + prepared_image: Box, } -impl<'a> AHash<'a> { - pub fn new(path: &'a Path, precision: &Precision, cache: &Option) -> Self { +impl AHash { + pub fn new(path: &Path, precision: &Precision, cache: &Option) -> Self { AHash { prepared_image: Box::new(prepare_image(&path, &HashType::AHash, &precision, cache)), } } } -impl<'a> PerceptualHash for AHash<'a> { +impl PerceptualHash for AHash { /** * Calculate the ahash of the provided prepared image. * @@ -63,3 +62,6 @@ impl<'a> PerceptualHash for AHash<'a> { } } } + +#[cfg(test)] +mod tests {} diff --git a/src/hash/dhash.rs b/src/hash/dhash.rs index e7ea637..a4e0735 100644 --- a/src/hash/dhash.rs +++ b/src/hash/dhash.rs @@ -6,24 +6,23 @@ use std::path::Path; use cache::Cache; -use super::prepare_image; use super::{HashType, PerceptualHash, Precision, PreparedImage}; - use super::image::GenericImageView; +use super::prepare_image; -pub struct DHash<'a> { - prepared_image: Box>, +pub struct DHash { + prepared_image: Box, } -impl<'a> DHash<'a> { - pub fn new(path: &'a Path, precision: &Precision, cache: &Option) -> Self { +impl DHash { + pub fn new(path: &Path, precision: &Precision, cache: &Option) -> Self { DHash { prepared_image: Box::new(prepare_image(&path, &HashType::DHash, &precision, cache)), } } } -impl<'a> PerceptualHash for DHash<'a> { +impl PerceptualHash for DHash { /** * Calculate the dhash of the provided prepared image * @@ -69,3 +68,6 @@ impl<'a> PerceptualHash for DHash<'a> { } } } + +#[cfg(test)] +mod tests {} diff --git a/src/hash/mod.rs b/src/hash/mod.rs index 3cb9a42..ab30724 100644 --- a/src/hash/mod.rs +++ b/src/hash/mod.rs @@ -6,12 +6,17 @@ extern crate dft; extern crate image; -use self::image::FilterType; -use cache::Cache; use std::f64; use std::fmt; +use std::fmt::{Error, Formatter}; use std::path::Path; +use serde::export::fmt::Debug; + +use cache::Cache; + +use self::image::FilterType; + mod ahash; mod dhash; mod phash; @@ -42,30 +47,45 @@ const HAMMING_DISTANCE_SIMILARITY_LIMIT: u64 = 5u64; /** * Prepared image that can be used to generate hashes */ -pub struct PreparedImage<'a> { - orig_path: &'a str, +pub struct PreparedImage { + orig_path: String, image: Option, } /** * Wraps the various perceptual hashes */ -pub struct PerceptualHashes<'a> { - pub orig_path: &'a str, +#[derive(Debug)] +pub struct PerceptualHashes { + pub orig_path: String, pub ahash: u64, pub dhash: u64, - pub phash: u64, + pub phash: u64 } -impl<'a> PerceptualHashes<'a> { - pub fn similar(&self, other: &'a PerceptualHashes<'a>) -> bool { +impl PartialEq for PerceptualHashes { + fn eq(&self, other: &Self) -> bool { + return self.ahash == other.ahash + && self.dhash == other.dhash + && self.phash == other.phash; + } + + fn ne(&self, other: &Self) -> bool { + return self.ahash != other.ahash + || self.dhash != other.dhash + || self.phash != other.phash; + } +} + +impl PerceptualHashes { + pub fn similar(&self, other: &PerceptualHashes) -> bool { if self.orig_path != other.orig_path && calculate_hamming_distance(self.ahash, other.ahash) - <= HAMMING_DISTANCE_SIMILARITY_LIMIT + <= HAMMING_DISTANCE_SIMILARITY_LIMIT && calculate_hamming_distance(self.dhash, other.dhash) - <= HAMMING_DISTANCE_SIMILARITY_LIMIT + <= HAMMING_DISTANCE_SIMILARITY_LIMIT && calculate_hamming_distance(self.phash, other.phash) - <= HAMMING_DISTANCE_SIMILARITY_LIMIT + <= HAMMING_DISTANCE_SIMILARITY_LIMIT { true } else { @@ -81,7 +101,7 @@ impl<'a> PerceptualHashes<'a> { * Medium aims for 64 bit precision * High aims for 128 bit precision */ -#[allow(dead_code)] +#[derive(Copy, Clone)] pub enum Precision { Low, Medium, @@ -89,7 +109,6 @@ pub enum Precision { } // Get the size of the required image -// impl Precision { fn get_size(&self) -> u32 { match *self { @@ -103,6 +122,7 @@ impl Precision { /** * Types of hashes supported */ +#[derive(Copy, Clone)] pub enum HashType { AHash, DHash, @@ -128,7 +148,7 @@ pub trait PerceptualHash { // Functions // /** - * Resonsible for parsing a path, converting an image and package it to be + * Responsible for parsing a path, converting an image and package it to be * hashed. * * # Arguments @@ -141,12 +161,12 @@ pub trait PerceptualHash { * A PreparedImage struct with the required information for performing hashing * */ -pub fn prepare_image<'a>( - path: &'a Path, +pub fn prepare_image( + path: &Path, hash_type: &HashType, precision: &Precision, cache: &Option, -) -> PreparedImage<'a> { +) -> PreparedImage { let image_path = path.to_str().unwrap(); let size: u32 = match *hash_type { HashType::PHash => precision.get_size() * 4, @@ -154,10 +174,10 @@ pub fn prepare_image<'a>( }; // Check if we have the already converted image in a cache and use that if possible. match *cache { - Some(ref c) => { - match c.get_image_from_cache(&path, size) { + Some(ref cache) => { + match cache.get_image_from_cache(&path, size) { Some(image) => PreparedImage { - orig_path: &*image_path, + orig_path: String::from(&*image_path), image: Some(image), }, None => { @@ -165,7 +185,7 @@ pub fn prepare_image<'a>( // Oh, and save it in a cache match processed_image.image { Some(ref image) => { - match c.put_image_in_cache(&path, size, &image) { + match cache.put_image_in_cache(&path, size, &image) { Ok(_) => {} Err(e) => println!("Unable to store image in cache. {}", e), }; @@ -197,7 +217,7 @@ fn process_image(image_path: &str, size: u32) -> PreparedImage { } }; PreparedImage { - orig_path: &*image_path, + orig_path: String::from(&*image_path), image, } } @@ -205,8 +225,8 @@ fn process_image(image_path: &str, size: u32) -> PreparedImage { /** * Get a specific HashType hash */ -pub fn get_perceptual_hash<'a>( - path: &'a Path, +pub fn get_perceptual_hash( + path: &Path, precision: &Precision, hash_type: &HashType, cache: &Option, @@ -221,20 +241,20 @@ pub fn get_perceptual_hash<'a>( /** * Get all perceptual hashes for an image */ -pub fn get_perceptual_hashes<'a>( - path: &'a Path, +pub fn get_perceptual_hashes( + path: &Path, precision: &Precision, cache: &Option, -) -> PerceptualHashes<'a> { +) -> PerceptualHashes { 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, + orig_path: String::from(&*image_path), + ahash, + dhash, + phash, } } @@ -248,3 +268,26 @@ pub fn calculate_hamming_distance(hash1: u64, hash2: u64) -> u64 { // the number of 1's in the difference to determine the hamming distance (hash1 ^ hash2).count_ones() as u64 } + +#[cfg(test)] +mod tests { + use hash::calculate_hamming_distance; + + #[test] + fn test_no_hamming_distance() { + let hamming_distance = calculate_hamming_distance(0, 0); + assert_eq!(hamming_distance, 0); + } + + #[test] + fn test_one_hamming_distance() { + let hamming_distance = calculate_hamming_distance(0, 1); + assert_eq!(hamming_distance, 1); + } + + #[test] + fn test_two_hamming_distance() { + let hamming_distance = calculate_hamming_distance(0, 3); + assert_eq!(hamming_distance, 2); + } +} diff --git a/src/hash/phash.rs b/src/hash/phash.rs index b05337d..f9940d3 100644 --- a/src/hash/phash.rs +++ b/src/hash/phash.rs @@ -6,25 +6,25 @@ use std::path::Path; use cache::Cache; +use super::{HashType, PerceptualHash, Precision, PreparedImage}; use super::dft; use super::dft::Transform; use super::image::{DynamicImage, GenericImageView, Pixel}; use super::prepare_image; -use super::{HashType, PerceptualHash, Precision, PreparedImage}; -pub struct PHash<'a> { - prepared_image: Box>, +pub struct PHash { + prepared_image: Box, } -impl<'a> PHash<'a> { - pub fn new(path: &'a Path, precision: &Precision, cache: &Option) -> Self { +impl PHash { + pub fn new(path: &Path, precision: &Precision, cache: &Option) -> Self { PHash { prepared_image: Box::new(prepare_image(&path, &HashType::PHash, &precision, cache)), } } } -impl<'a> PerceptualHash for PHash<'a> { +impl PerceptualHash for PHash { /** * Calculate the phash of the provided prepared image * @@ -41,18 +41,18 @@ impl<'a> PerceptualHash for PHash<'a> { // Get 2d data to 2d FFT/DFT // Either from the cache or calculate it // Pretty fast already, so caching doesn't make a huge difference - // Atleast compared to opening and processing the images + // At least compared to opening and processing the images let data_matrix: Vec> = match *cache { Some(ref c) => { match c.get_matrix_from_cache( - &Path::new(self.prepared_image.orig_path), + &Path::new(&self.prepared_image.orig_path), width as u32, ) { Some(matrix) => matrix, None => { let matrix = create_data_matrix(width, height, &image); match c.put_matrix_in_cache( - &Path::new(self.prepared_image.orig_path), + &Path::new(&self.prepared_image.orig_path), width as u32, &matrix, ) { @@ -117,11 +117,11 @@ fn create_data_matrix(width: u32, height: u32, image: &DynamicImage) -> Vec f64 { } } -#[test] -fn test_2d_dft() { - let mut test_matrix: Vec> = Vec::new(); - test_matrix.push(vec![1f64, 1f64, 1f64, 3f64]); - test_matrix.push(vec![1f64, 2f64, 2f64, 1f64]); - test_matrix.push(vec![1f64, 2f64, 2f64, 1f64]); - test_matrix.push(vec![3f64, 1f64, 1f64, 1f64]); - - println!("{:?}", test_matrix[0]); - println!("{:?}", test_matrix[1]); - println!("{:?}", test_matrix[2]); - println!("{:?}", test_matrix[3]); - - println!("Performing 2d DFT"); - calculate_2d_dft(&mut test_matrix); - - println!("{:?}", test_matrix[0]); - println!("{:?}", test_matrix[1]); - println!("{:?}", test_matrix[2]); - println!("{:?}", test_matrix[3]); - - assert!(test_matrix[0][0] == 24_f64); - assert!(test_matrix[0][1] == 0_f64); - assert!(test_matrix[0][2] == 0_f64); - assert!(test_matrix[0][3] == 0_f64); - - assert!(test_matrix[1][0] == 0_f64); - assert!(test_matrix[1][1] == 0_f64); - assert!(test_matrix[1][2] == -2_f64); - assert!(test_matrix[1][3] == 2_f64); - - assert!(test_matrix[2][0] == 0_f64); - assert!(test_matrix[2][1] == -2_f64); - assert!(test_matrix[2][2] == -4_f64); - assert!(test_matrix[2][3] == -2_f64); - - assert!(test_matrix[3][0] == 0_f64); - assert!(test_matrix[3][1] == 2_f64); - assert!(test_matrix[3][2] == -2_f64); - assert!(test_matrix[3][3] == 0_f64); +#[cfg(test)] +mod tests { + use hash::phash::calculate_2d_dft; + + #[test] + fn test_2d_dft() { + let mut test_matrix: Vec> = Vec::new(); + test_matrix.push(vec![1f64, 1f64, 1f64, 3f64]); + test_matrix.push(vec![1f64, 2f64, 2f64, 1f64]); + test_matrix.push(vec![1f64, 2f64, 2f64, 1f64]); + test_matrix.push(vec![3f64, 1f64, 1f64, 1f64]); + + println!("2d matrix before DFT"); + println!("{:?}", test_matrix[0]); + println!("{:?}", test_matrix[1]); + println!("{:?}", test_matrix[2]); + println!("{:?}", test_matrix[3]); + + println!("Performing 2d DFT"); + calculate_2d_dft(&mut test_matrix); + + println!("2d matrix after DFT"); + println!("{:?}", test_matrix[0]); + println!("{:?}", test_matrix[1]); + println!("{:?}", test_matrix[2]); + println!("{:?}", test_matrix[3]); + + assert_eq!(test_matrix[0][0], 24_f64); + assert_eq!(test_matrix[0][1], 0_f64); + assert_eq!(test_matrix[0][2], 0_f64); + assert_eq!(test_matrix[0][3], 0_f64); + + assert_eq!(test_matrix[1][0], 0_f64); + assert_eq!(test_matrix[1][1], 0_f64); + assert_eq!(test_matrix[1][2], -2_f64); + assert_eq!(test_matrix[1][3], 2_f64); + + assert_eq!(test_matrix[2][0], 0_f64); + assert_eq!(test_matrix[2][1], -2_f64); + assert_eq!(test_matrix[2][2], -4_f64); + assert_eq!(test_matrix[2][3], -2_f64); + + assert_eq!(test_matrix[3][0], 0_f64); + assert_eq!(test_matrix[3][1], 2_f64); + assert_eq!(test_matrix[3][2], -2_f64); + assert_eq!(test_matrix[3][3], 0_f64); + } } diff --git a/src/lib.rs b/src/lib.rs index b3c806b..2ad327d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ extern crate libc; extern crate rustc_serialize; +extern crate serde; #[cfg(feature = "bench")] extern crate test; @@ -20,20 +21,20 @@ pub mod cache; pub mod hash; #[repr(C)] -pub struct PIHash<'a> { - cache: Option>, +pub struct PIHash { + cache: Option, } -impl<'a> PIHash<'a> { +impl PIHash { /** * Create a new pihash library, and initialize a cache of a path is passed. * If none is passed then no cache is initialized or used with the library */ - pub fn new(cache_path: Option<&'a str>) -> PIHash<'a> { + pub fn new(cache_path: Option<&str>) -> PIHash { match cache_path { Some(path) => { let cache = Cache { - cache_dir: path, + cache_dir: String::from(path), use_cache: true, }; match cache.init() { @@ -57,7 +58,7 @@ impl<'a> PIHash<'a> { hash::get_perceptual_hash(&path, &precision, &hash_type, &self.cache) } - pub fn get_phashes(&self, path: &'a Path) -> hash::PerceptualHashes { + pub fn get_pihashes(&self, path: &Path) -> hash::PerceptualHashes { hash::get_perceptual_hashes(&path, &hash::Precision::Medium, &self.cache) } @@ -159,24 +160,24 @@ pub struct PIHashes { } #[no_mangle] -pub extern "C" fn ext_get_phashes(lib: &PIHash, path_char: *const libc::c_char) -> *mut PIHashes { +pub extern "C" fn ext_get_pihashes(lib: &PIHash, path_char: *const libc::c_char) -> *mut PIHashes { unsafe { let path_str = CStr::from_ptr(path_char); let image_path = get_str_from_cstr(path_str); let path = Path::new(&image_path); - let phashes = lib.get_phashes(path); + let pihashes = lib.get_pihashes(path); Box::into_raw(Box::new(PIHashes { - ahash: phashes.ahash, - dhash: phashes.dhash, - phash: phashes.phash, + ahash: pihashes.ahash, + dhash: pihashes.dhash, + phash: pihashes.phash, })) } } #[no_mangle] -pub extern "C" fn ext_free_phashes(raw_phashes: *const libc::c_void) { +pub extern "C" fn ext_free_pihashes(raw_pihashes: *const libc::c_void) { unsafe { - drop(Box::from_raw(raw_phashes as *mut PIHashes)); + drop(Box::from_raw(raw_pihashes as *mut PIHashes)); } } @@ -213,10 +214,14 @@ mod tests { use cache; use hash; + use hash::{PerceptualHash, PerceptualHashes}; + use super::PIHash; #[cfg(feature = "bench")] use super::test::Bencher; - use super::PIHash; + + thread_local!(static LIB: PIHash = PIHash::new(Some(cache::DEFAULT_CACHE_DIR))); + thread_local!(static NO_CACHE_LIB: PIHash = PIHash::new(None)); #[test] fn test_can_get_test_images() { @@ -247,7 +252,7 @@ mod tests { * Updated test function. Assumes 3 images to a set and no hamming distances. * We don't need to confirm that the hamming distance calculation works in these tests. */ - fn test_imageset_hash( + fn test_image_set_hash( hash_type: hash::HashType, hash_precision: hash::Precision, max_hamming_distance: u64, @@ -257,7 +262,7 @@ mod tests { ) { let mut hashes: [u64; 3] = [0; 3]; for index in 0..image_paths.len() { -// println!("{}, {:?}", index, image_paths[index]); + // println!("{}, {:?}", index, image_paths[index]); let image_path = image_paths[index]; let calculated_hash = lib.get_perceptual_hash(&image_path, &hash_precision, &hash_type); println!( @@ -288,37 +293,116 @@ mod tests { } } - #[test] - fn test_confirm_ahash_results() { - // Prep_library - let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR)); - let no_cache_lib = PIHash::new(None); + /** + * Test image set with and without caching + */ + fn test_image_set( + hash_type: hash::HashType, + hash_precision: hash::Precision, + max_hamming_distance: u64, + image_paths: [&Path; 3], + image_hashes: [u64; 3], + ) { + LIB.with(|lib| { + test_image_set_hash( + hash_type, + hash_precision, + max_hamming_distance, + image_paths, + image_hashes, + lib, + ); + }); + NO_CACHE_LIB.with(|lib| { + test_image_set_hash( + hash_type, + hash_precision, + max_hamming_distance, + image_paths, + image_hashes, + lib, + ); + }); + } - // Sample_01 tests + /** + * Updated test function. Assumes 3 images to a set and no hamming distances. + * We don't need to confirm that the hamming distance calculation works in these tests. + */ + fn test_images_hashes( + image_hashes: &[PerceptualHashes], + lib: &PIHash, + ) { + let mut hashes = vec![]; + for index in 0..image_hashes.len() { +// println!("{}, {:?}", index, image_paths[index]); + let image_path = Path::new(&image_hashes[index].orig_path); + let calculated_hash = lib.get_pihashes(&image_path); + println!( + "Image hashes expected: [{:?}] actual: [{:?}]", + image_hashes[index], + calculated_hash + ); + hashes.push(calculated_hash); + } + for index in 0..image_hashes.len() { + assert_eq!(hashes[index], image_hashes[index]); + } +// +// for index in 0..hashes.len() { +// for index2 in 0..hashes.len() { +// if index == index2 { +// continue; +// } else { +// let distance = hash::calculate_hamming_distance(hashes[index], hashes[index2]); +// println!("Hashes [{}] and [{}] have a hamming distance of [{}] of a max allowed distance of [{}]", +// hashes[index], +// hashes[index2], +// distance, +// max_hamming_distance); +// assert!(distance <= max_hamming_distance); +// } +// } +// } + } + + /** + * Test images with and without caching + */ + fn test_images(image_hashes: &[PerceptualHashes]) { + LIB.with(|lib| { + test_images_hashes( + &image_hashes, + lib, + ); + }); + NO_CACHE_LIB.with(|lib| { + test_images_hashes( + &image_hashes, + lib, + ); + }); + } + + #[test] + fn test_confirm_ahash_results_sample_01() { let sample_01_images: [&Path; 3] = [ &Path::new("./test_images/sample_01_large.jpg"), &Path::new("./test_images/sample_01_medium.jpg"), &Path::new("./test_images/sample_01_small.jpg"), ]; let sample_01_hashes: [u64; 3] = [857051991849750, 857051991849750, 857051991849750]; - test_imageset_hash( + test_image_set( hash::HashType::AHash, hash::Precision::Medium, 0u64, sample_01_images, sample_01_hashes, - &lib, - ); - test_imageset_hash( - hash::HashType::AHash, - hash::Precision::Medium, - 0u64, - sample_01_images, - sample_01_hashes, - &no_cache_lib, ); + } - // Sample_02 tests + #[test] + fn test_confirm_ahash_results_sample_02() { let sample_02_images: [&Path; 3] = [ &Path::new("./test_images/sample_02_large.jpg"), &Path::new("./test_images/sample_02_medium.jpg"), @@ -329,24 +413,17 @@ mod tests { 18446744073441116160, 18446744073441116160, ]; - test_imageset_hash( - hash::HashType::AHash, - hash::Precision::Medium, - 0u64, - sample_02_images, - sample_02_hashes, - &lib, - ); - test_imageset_hash( + test_image_set( hash::HashType::AHash, hash::Precision::Medium, 0u64, sample_02_images, sample_02_hashes, - &no_cache_lib, ); + } - // Sample_03 tests + #[test] + fn test_confirm_ahash_results_sample_03() { let sample_03_images: [&Path; 3] = [ &Path::new("./test_images/sample_03_large.jpg"), &Path::new("./test_images/sample_03_medium.jpg"), @@ -354,24 +431,17 @@ mod tests { ]; let sample_03_hashes: [u64; 3] = [135670932300497406, 135670932300497406, 135670932300497406]; - test_imageset_hash( - hash::HashType::AHash, - hash::Precision::Medium, - 0u64, - sample_03_images, - sample_03_hashes, - &lib, - ); - test_imageset_hash( + test_image_set( hash::HashType::AHash, hash::Precision::Medium, 0u64, sample_03_images, sample_03_hashes, - &no_cache_lib, ); + } - // Sample_04 tests + #[test] + fn test_confirm_ahash_results_sample_04() { let sample_04_images: [&Path; 3] = [ &Path::new("./test_images/sample_04_large.jpg"), &Path::new("./test_images/sample_04_medium.jpg"), @@ -382,34 +452,20 @@ mod tests { 18446460933225054208, 18446460933225054208, ]; - test_imageset_hash( - hash::HashType::AHash, - hash::Precision::Medium, - 0u64, - sample_04_images, - sample_04_hashes, - &lib, - ); - test_imageset_hash( - hash::HashType::AHash, - hash::Precision::Medium, - 0u64, - sample_04_images, - sample_04_hashes, - &no_cache_lib, - ); - - // Clean_Cache - // super::teardown(); + LIB.with(|lib| { + test_image_set_hash( + hash::HashType::AHash, + hash::Precision::Medium, + 0u64, + sample_04_images, + sample_04_hashes, + lib, + ); + }); } #[test] - fn test_confirm_dhash_results() { - // Prep_library - let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR)); - let no_cache_lib = PIHash::new(None); - - // Sample_01 tests + fn test_confirm_dhash_results_sample_01() { let sample_01_images: [&Path; 3] = [ &Path::new("./test_images/sample_01_large.jpg"), &Path::new("./test_images/sample_01_medium.jpg"), @@ -420,24 +476,20 @@ mod tests { 3404580580803739582, 3404580580803739582, ]; - test_imageset_hash( - hash::HashType::DHash, - hash::Precision::Medium, - 0u64, - sample_01_images, - sample_01_hashes, - &lib, - ); - test_imageset_hash( - hash::HashType::DHash, - hash::Precision::Medium, - 0u64, - sample_01_images, - sample_01_hashes, - &no_cache_lib, - ); + LIB.with(|lib| { + test_image_set_hash( + hash::HashType::DHash, + hash::Precision::Medium, + 0u64, + sample_01_images, + sample_01_hashes, + lib, + ); + }); + } - // Sample_02 tests + #[test] + fn test_confirm_dhash_results_sample_02() { let sample_02_images: [&Path; 3] = [ &Path::new("./test_images/sample_02_large.jpg"), &Path::new("./test_images/sample_02_medium.jpg"), @@ -448,24 +500,20 @@ mod tests { 14726771606135242753, 14726771606135242753, ]; - test_imageset_hash( - hash::HashType::DHash, - hash::Precision::Medium, - 0u64, - sample_02_images, - sample_02_hashes, - &lib, - ); - test_imageset_hash( - hash::HashType::DHash, - hash::Precision::Medium, - 0u64, - sample_02_images, - sample_02_hashes, - &no_cache_lib, - ); + LIB.with(|lib| { + test_image_set_hash( + hash::HashType::DHash, + hash::Precision::Medium, + 0u64, + sample_02_images, + sample_02_hashes, + lib, + ); + }); + } - // Sample_03 tests + #[test] + fn test_confirm_dhash_results_sample_03() { let sample_03_images: [&Path; 3] = [ &Path::new("./test_images/sample_03_large.jpg"), &Path::new("./test_images/sample_03_medium.jpg"), @@ -473,24 +521,20 @@ mod tests { ]; let sample_03_hashes: [u64; 3] = [144115181601817086, 144115181601817086, 144115181601817086]; - test_imageset_hash( - hash::HashType::DHash, - hash::Precision::Medium, - 0u64, - sample_03_images, - sample_03_hashes, - &lib, - ); - test_imageset_hash( - hash::HashType::DHash, - hash::Precision::Medium, - 0u64, - sample_03_images, - sample_03_hashes, - &no_cache_lib, - ); + LIB.with(|lib| { + test_image_set_hash( + hash::HashType::DHash, + hash::Precision::Medium, + 0u64, + sample_03_images, + sample_03_hashes, + lib, + ); + }); + } - // Sample_04 tests + #[test] + fn test_confirm_dhash_results_sample_04() { let sample_04_images: [&Path; 3] = [ &Path::new("./test_images/sample_04_large.jpg"), &Path::new("./test_images/sample_04_medium.jpg"), @@ -501,58 +545,37 @@ mod tests { 18374262188442386433, 18374262188442386433, ]; - test_imageset_hash( - hash::HashType::DHash, - hash::Precision::Medium, - 0u64, - sample_04_images, - sample_04_hashes, - &lib, - ); - test_imageset_hash( - hash::HashType::DHash, - hash::Precision::Medium, - 0u64, - sample_04_images, - sample_04_hashes, - &no_cache_lib, - ); - - // Clean_Cache - // super::teardown(); + LIB.with(|lib| { + test_image_set_hash( + hash::HashType::DHash, + hash::Precision::Medium, + 0u64, + sample_04_images, + sample_04_hashes, + lib, + ); + }); } #[test] - fn test_confirm_phash_results() { - // Prep_library - let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR)); - let no_cache_lib = PIHash::new(None); - - // Sample_01 tests + fn test_confirm_phash_results_sample_01() { let sample_01_images: [&Path; 3] = [ &Path::new("./test_images/sample_01_large.jpg"), &Path::new("./test_images/sample_01_medium.jpg"), &Path::new("./test_images/sample_01_small.jpg"), ]; let sample_01_hashes: [u64; 3] = [72357778504597504, 72357778504597504, 72357778504597504]; - test_imageset_hash( - hash::HashType::PHash, - hash::Precision::Medium, - 0u64, - sample_01_images, - sample_01_hashes, - &lib, - ); - test_imageset_hash( + test_image_set( hash::HashType::PHash, hash::Precision::Medium, 0u64, sample_01_images, sample_01_hashes, - &no_cache_lib, ); + } - // Sample_02 tests + #[test] + fn test_confirm_phash_results_sample_02() { let sample_02_images: [&Path; 3] = [ &Path::new("./test_images/sample_02_large.jpg"), &Path::new("./test_images/sample_02_medium.jpg"), @@ -563,24 +586,17 @@ mod tests { 5332332327550844928, 5332332327550844928, ]; - test_imageset_hash( + test_image_set( hash::HashType::PHash, hash::Precision::Medium, 0u64, sample_02_images, sample_02_hashes, - &lib, - ); - test_imageset_hash( - hash::HashType::PHash, - hash::Precision::Medium, - 0u64, - sample_02_images, - sample_02_hashes, - &no_cache_lib, ); + } - // Sample_03 tests + #[test] + fn test_confirm_phash_results_sample_03() { let sample_03_images: [&Path; 3] = [ &Path::new("./test_images/sample_03_large.jpg"), &Path::new("./test_images/sample_03_medium.jpg"), @@ -591,24 +607,17 @@ mod tests { 6917529027641081856, 6917529027641081856, ]; - test_imageset_hash( + test_image_set( hash::HashType::PHash, hash::Precision::Medium, 0u64, sample_03_images, sample_03_hashes, - &lib, - ); - test_imageset_hash( - hash::HashType::PHash, - hash::Precision::Medium, - 0u64, - sample_03_images, - sample_03_hashes, - &no_cache_lib, ); + } - // Sample_04 tests + #[test] + fn test_confirm_phash_results_sample_04() { let sample_04_images: [&Path; 3] = [ &Path::new("./test_images/sample_04_large.jpg"), &Path::new("./test_images/sample_04_medium.jpg"), @@ -619,25 +628,44 @@ mod tests { 10997931646002397184, 10997931646002397184, ]; - test_imageset_hash( + test_image_set( hash::HashType::PHash, hash::Precision::Medium, 0u64, sample_04_images, sample_04_hashes, - &lib, - ); - test_imageset_hash( - hash::HashType::PHash, - hash::Precision::Medium, - 0u64, - sample_04_images, - sample_04_hashes, - &no_cache_lib, ); + } - // Clean_Cache - // super::teardown(); + #[test] + fn test_confirm_pihash_results() { + let sample_hashes: [PerceptualHashes; 4] = [ + PerceptualHashes { + orig_path: "./test_images/sample_01_large.jpg".to_string(), + ahash: 857051991849750, + dhash: 3404580580803739582, + phash: 72357778504597504, + }, + PerceptualHashes { + orig_path: "./test_images/sample_02_large.jpg".to_string(), + ahash: 18446744073441116160, + dhash: 14726771606135242753, + phash: 5332332327550844928, + }, + PerceptualHashes { + orig_path: "./test_images/sample_03_large.jpg".to_string(), + ahash: 135670932300497406, + dhash: 144115181601817086, + phash: 6917529027641081856, + }, + PerceptualHashes { + orig_path: "./test_images/sample_04_large.jpg".to_string(), + ahash: 18446460933225054208, + dhash: 18374262188442386433, + phash: 10997931646002397184, + } + ]; + test_images(&sample_hashes); } #[cfg(feature = "bench")] @@ -647,7 +675,7 @@ mod tests { let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR)); // Setup the caches to make sure we're good to properly bench - // All phashes so that the matricies are pulled from cache as well + // All pihashes so that the matrices are pulled from cache as well lib.get_perceptual_hash( &Path::new("./test_images/sample_01_large.jpg"), &hash::Precision::Medium, @@ -655,7 +683,6 @@ mod tests { ); bench.iter(|| { - // Sample_01 Bench lib.get_perceptual_hash( &Path::new("./test_images/sample_01_large.jpg"), &hash::Precision::Medium, @@ -671,7 +698,6 @@ mod tests { let lib = PIHash::new(None); bench.iter(|| { - // Sample_01 Bench lib.get_perceptual_hash( &Path::new("./test_images/sample_01_large.jpg"), &hash::Precision::Medium, diff --git a/src/main.rs b/src/main.rs index 36bc334..5c2d9de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,9 +9,10 @@ extern crate rustc_serialize; #[macro_use] extern crate serde_derive; -use docopt::Docopt; use std::path::Path; +use docopt::Docopt; + // Getting the version information from cargo during compile time const VERSION: &'static str = env!("CARGO_PKG_VERSION"); @@ -32,6 +33,7 @@ Options: -a, --ahash Include an ahash calculation. -d, --dhash Include an dhash calculation. -p, --phash Include an phash calculation. + -n, --nocache Disable caching behavior. "; #[derive(Debug, Deserialize)] @@ -42,6 +44,7 @@ struct Args { flag_phash: bool, arg_path: String, arg_comparison: Vec, + flag_nocache: bool, } fn main() { @@ -55,8 +58,14 @@ fn main() { std::process::exit(0); } + let cache = if args.flag_nocache { + None + } else { + Some(pihash::cache::DEFAULT_CACHE_DIR) + }; + // Init the hashing library - let lib = pihash::PIHash::new(Some(pihash::cache::DEFAULT_CACHE_DIR)); + let lib = pihash::PIHash::new(cache); // println!("{:?}", args); if args.arg_comparison.len() > 0 { @@ -72,10 +81,10 @@ fn main() { )); } - let mut similar_images: Vec<&str> = Vec::new(); + let mut similar_images: Vec = Vec::new(); for comparison_hash in comparison_hashes { if base_hash.similar(&comparison_hash) { - similar_images.push(&comparison_hash.orig_path); + similar_images.push(String::from(&comparison_hash.orig_path)); } } @@ -106,11 +115,11 @@ fn flags_get_all_perceptual_hashes(args: &Args) -> bool { || (!args.flag_ahash && !args.flag_dhash && !args.flag_phash) } -fn get_requested_perceptual_hashes<'a>( +fn get_requested_perceptual_hashes( lib: &pihash::PIHash, - image_path: &'a Path, + image_path: &Path, args: &Args, -) -> pihash::hash::PerceptualHashes<'a> { +) -> pihash::hash::PerceptualHashes { let ahash = if args.flag_ahash || flags_get_all_perceptual_hashes(&args) { lib.get_ahash(&image_path) } else { @@ -130,9 +139,9 @@ fn get_requested_perceptual_hashes<'a>( }; pihash::hash::PerceptualHashes { - orig_path: image_path.to_str().unwrap(), - ahash: ahash, - dhash: dhash, - phash: phash, + orig_path: String::from(image_path.to_str().unwrap()), + ahash, + dhash, + phash, } }