diff --git a/Cargo.toml b/Cargo.toml index c19daac..d6fc879 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pihash" -version = "0.2.9" +version = "0.3.0" authors = ["Drew Short "] description = "A simple library for generating perceptual hashes for images and comparing images based on their perceptual hashes." repository = "https://github.com/warricksothr/Perceptual-Image-Hashing/" @@ -12,6 +12,13 @@ exclude = ["test_images/*"] [lib] crate-type = ["dylib", "rlib"] +[features] +default = [] + +# Feature to allow bench testing on nightly +# But still allow the code to build on beta/stable +bench = [] + [dependencies] docopt = "0.6.78" rustc-serialize = "0.3.18" diff --git a/src/cache.rs b/src/cache.rs index 4204532..5d1806e 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -21,7 +21,7 @@ use std::option::Option; use std::result::Result; use super::rustc_serialize::json; -pub const CACHE_DIR: &'static str = "./.hash_cache"; +pub const DEFAULT_CACHE_DIR: &'static str = "./.hash_cache"; const CACHED_IMAGE_EXT: &'static str = "png"; const CACHED_MATRIX_EXT: &'static str = "dft"; // Caching version information @@ -60,7 +60,7 @@ pub struct Cache<'a> { impl<'a> Default for Cache<'a> { fn default() -> Cache<'a> { Cache { - cache_dir: CACHE_DIR, + cache_dir: DEFAULT_CACHE_DIR, use_cache: true, } } @@ -154,7 +154,7 @@ impl<'a> Cache<'a> { match hash { Ok(sha1) => { let cache_path_str = format!("{}/image/{}x{}/{}.{}", - CACHE_DIR, + self.cache_dir, size, size, sha1, @@ -297,7 +297,7 @@ impl<'a> Cache<'a> { Ok(sha1) => { // Check if the file exists in the cache let cache_path_str = format!("{}/matrix/{}x{}/{}.{}", - CACHE_DIR, + self.cache_dir, size, size, sha1, diff --git a/src/hash/ahash.rs b/src/hash/ahash.rs index c41d080..c891baf 100644 --- a/src/hash/ahash.rs +++ b/src/hash/ahash.rs @@ -14,9 +14,9 @@ pub struct AHash<'a> { } impl<'a> AHash<'a> { - pub fn new(path: &'a Path, precision: &Precision, cache: &'a Cache) -> Self { + pub fn new(path: &'a Path, precision: &Precision, cache: &Option>) -> Self { AHash { - prepared_image: Box::new(prepare_image(&path, &HashType::AHash, &precision, &cache)), + prepared_image: Box::new(prepare_image(&path, &HashType::AHash, &precision, cache)), } } } @@ -29,7 +29,7 @@ impl<'a> PerceptualHash for AHash<'a> { * * A u64 representing the value of the hash */ - fn get_hash(&self) -> u64 { + fn get_hash(&self, _: &Option>) -> u64 { let (width, height) = self.prepared_image.image.dimensions(); // calculating the average pixel value diff --git a/src/hash/dhash.rs b/src/hash/dhash.rs index 974aed0..651b4fc 100644 --- a/src/hash/dhash.rs +++ b/src/hash/dhash.rs @@ -14,9 +14,9 @@ pub struct DHash<'a> { } impl<'a> DHash<'a> { - pub fn new(path: &'a Path, precision: &Precision, cache: &'a Cache) -> Self { + pub fn new(path: &'a Path, precision: &Precision, cache: &Option>) -> Self { DHash { - prepared_image: Box::new(prepare_image(&path, &HashType::DHash, &precision, &cache)), + prepared_image: Box::new(prepare_image(&path, &HashType::DHash, &precision, cache)), } } } @@ -29,7 +29,7 @@ impl<'a> PerceptualHash for DHash<'a> { * * Returns a u64 representing the value of the hash */ - fn get_hash(&self) -> u64 { + fn get_hash(&self, _: &Option>) -> u64 { // Stored for later let first_pixel_val = self.prepared_image.image.pixels().nth(0).unwrap().channels()[0]; let last_pixel_val = self.prepared_image.image.pixels().last().unwrap().channels()[0]; diff --git a/src/hash/mod.rs b/src/hash/mod.rs index 221a4a4..639ee6a 100644 --- a/src/hash/mod.rs +++ b/src/hash/mod.rs @@ -44,7 +44,6 @@ const HAMMING_DISTANCE_SIMILARITY_LIMIT: u64 = 5u64; pub struct PreparedImage<'a> { orig_path: &'a str, image: image::ImageBuffer, Vec>, - cache: &'a Cache<'a>, } /** @@ -108,7 +107,7 @@ pub enum HashType { // Traits // pub trait PerceptualHash { - fn get_hash(&self) -> u64; + fn get_hash(&self, cache: &Option>) -> u64; } // Functions // @@ -130,7 +129,7 @@ pub trait PerceptualHash { pub fn prepare_image<'a>(path: &'a Path, hash_type: &HashType, precision: &Precision, - cache: &'a Cache<'a>) + cache: &Option>) -> PreparedImage<'a> { let image_path = path.to_str().unwrap(); let size: u32 = match *hash_type { @@ -138,29 +137,43 @@ pub fn prepare_image<'a>(path: &'a Path, _ => precision.get_size(), }; // Check if we have the already converted image in a cache and use that if possible. - match cache.get_image_from_cache(&path, size) { - Some(image) => { - PreparedImage { - orig_path: &*image_path, - image: image, - cache: &cache, + match *cache { + Some(ref c) => { + match c.get_image_from_cache(&path, size) { + Some(image) => { + PreparedImage { + orig_path: &*image_path, + image: image, + } + } + None => { + let image = process_image(&path, size); + // Oh, and save it in a cache + match c.put_image_in_cache(&path, size, &image.image) { + Ok(_) => {} + Err(e) => println!("Unable to store image in cache. {}", e), + }; + image + } } - } - None => { - // Otherwise let's do that work now and store it. - let image = image::open(path).unwrap(); - let small_image = image.resize_exact(size, size, FilterType::Lanczos3); - let grey_image = small_image.to_luma(); - match cache.put_image_in_cache(&path, size, &grey_image) { - Ok(_) => {} - Err(e) => println!("Unable to store image in cache. {}", e), - }; - PreparedImage { - orig_path: &*image_path, - image: grey_image, - cache: &cache, - } - } + }, + None => process_image(&path, size), + } +} + +/** + * Turn the image into something we can work with + */ +fn process_image(path: &Path, + size: u32) -> PreparedImage{ + // Otherwise let's do that work now and store it. + let image_path = path.to_str().unwrap(); + let image = image::open(path).unwrap(); + let small_image = image.resize_exact(size, size, FilterType::Lanczos3); + let grey_image = small_image.to_luma(); + PreparedImage { + orig_path: &*image_path, + image: grey_image, } } @@ -170,12 +183,12 @@ pub fn prepare_image<'a>(path: &'a Path, pub fn get_perceptual_hash<'a>(path: &'a Path, precision: &Precision, hash_type: &HashType, - cache: &Cache) + cache: &Option>) -> u64 { match *hash_type { - HashType::AHash => ahash::AHash::new(&path, &precision, &cache).get_hash(), - HashType::DHash => dhash::DHash::new(&path, &precision, &cache).get_hash(), - HashType::PHash => phash::PHash::new(&path, &precision, &cache).get_hash(), + 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), } } @@ -184,12 +197,12 @@ pub fn get_perceptual_hash<'a>(path: &'a Path, */ pub fn get_perceptual_hashes<'a>(path: &'a Path, precision: &Precision, - cache: &Cache) + cache: &Option>) -> PerceptualHashes<'a> { let image_path = path.to_str().unwrap(); - let ahash = ahash::AHash::new(&path, &precision, &cache).get_hash(); - let dhash = dhash::DHash::new(&path, &precision, &cache).get_hash(); - let phash = phash::PHash::new(&path, &precision, &cache).get_hash(); + 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, diff --git a/src/hash/phash.rs b/src/hash/phash.rs index 28a1409..9e85b3b 100644 --- a/src/hash/phash.rs +++ b/src/hash/phash.rs @@ -16,9 +16,9 @@ pub struct PHash<'a> { } impl<'a> PHash<'a> { - pub fn new(path: &'a Path, precision: &Precision, cache: &'a Cache) -> Self { + pub fn new(path: &'a Path, precision: &Precision, cache: &Option>) -> Self { PHash { - prepared_image: Box::new(prepare_image(&path, &HashType::PHash, &precision, &cache)), + prepared_image: Box::new(prepare_image(&path, &HashType::PHash, &precision, cache)), } } } @@ -31,7 +31,7 @@ impl<'a> PerceptualHash for PHash<'a> { * * Returns a u64 representing the value of the hash */ - fn get_hash(&self) -> u64 { + fn get_hash(&self, cache: &Option>) -> u64 { // Get the image data into a vector to perform the DFT on. let width = self.prepared_image.image.width() as usize; let height = self.prepared_image.image.height() as usize; @@ -40,39 +40,23 @@ impl<'a> PerceptualHash for PHash<'a> { // 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 - let mut data_matrix: Vec> = Vec::new(); - match self.prepared_image - .cache - .get_matrix_from_cache(&Path::new(self.prepared_image.orig_path), width as u32) { - Some(matrix) => data_matrix = matrix, - None => { - // Preparing the results - for x in 0..width { - data_matrix.push(Vec::new()); - for y in 0..height { - let pos_x = x as u32; - let pos_y = y as u32; - data_matrix[x] - .push(self.prepared_image - .image - .get_pixel(pos_x, pos_y) - .channels()[0] as f64); + let data_matrix: Vec> = match *cache { + Some(ref c) => { + match c.get_matrix_from_cache(&Path::new(self.prepared_image.orig_path), width as u32) { + Some(matrix) => matrix, + None => { + let matrix = create_data_matrix(width, height, &self.prepared_image); + // Store this DFT in the cache + match c.put_matrix_in_cache(&Path::new(self.prepared_image.orig_path), width as u32, &matrix) { + Ok(_) => {} + Err(e) => println!("Unable to store matrix in cache. {}", e), + }; + matrix } } - - // Perform the 2D DFT operation on our matrix - calculate_2d_dft(&mut data_matrix); - // Store this DFT in the cache - match self.prepared_image - .cache - .put_matrix_in_cache(&Path::new(self.prepared_image.orig_path), - width as u32, - &data_matrix) { - Ok(_) => {} - Err(e) => println!("Unable to store matrix in cache. {}", e), - }; - } - } + }, + None => create_data_matrix(width, height, &self.prepared_image) + }; // Only need the top left quadrant let target_width = (width / 4) as usize; @@ -109,6 +93,27 @@ impl<'a> PerceptualHash for PHash<'a> { } } +fn create_data_matrix(width: usize, height: usize, prepared_image: &PreparedImage) -> Vec> { + let mut data_matrix: Vec> = Vec::new(); + // Preparing the results + for x in 0..width { + data_matrix.push(Vec::new()); + for y in 0..height { + let pos_x = x as u32; + let pos_y = y as u32; + data_matrix[x] + .push(prepared_image + .image + .get_pixel(pos_x, pos_y) + .channels()[0] as f64); + } + } + + // Perform the 2D DFT operation on our matrix + calculate_2d_dft(&mut data_matrix); + data_matrix +} + // Use a 1D DFT to cacluate the 2D DFT. // // This is achieved by calculating the DFT for each row, then calculating the diff --git a/src/lib.rs b/src/lib.rs index 2a1438a..81e5dcb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,138 +5,168 @@ extern crate libc; extern crate rustc_serialize; +#[cfg(feature = "bench")] +extern crate test; pub mod hash; -mod cache; +pub mod cache; use std::path::Path; use std::ffi::CStr; use cache::Cache; -static LIB_CACHE: Cache<'static> = Cache { - cache_dir: cache::CACHE_DIR, - use_cache: true, -}; +pub struct PIHash<'a> { + cache: Option>> +} -/** - * Prepare the library for work. - * - * Not performing this step may cause parts to fail. - */ -#[no_mangle] -pub extern "C" fn init() { - match LIB_CACHE.init() { - Ok(_) => {} - Err(e) => println!("Error: {}", e), - } +impl<'a> PIHash<'a> { + /** + * 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> { + match cache_path { + Some(path) => { + let cache = Cache { cache_dir: path, use_cache: true}; + match cache.init() { + Ok(_) => PIHash { cache: Some(Box::new(cache)) }, + Err(e) => { + println!("Error creating library with cache: {}", e); + PIHash { cache: None } + } + } + }, + None => PIHash { cache: None }, + } + } + + pub fn get_perceptual_hash(&self, path: &Path, precision: &hash::Precision, hash_type: &hash::HashType) -> u64 { + hash::get_perceptual_hash(&path, &precision, &hash_type, &self.cache) + } + + pub fn get_phashes(&self, path: &'a Path) -> hash::PerceptualHashes { + hash::get_perceptual_hashes(path, &hash::Precision::Medium, &self.cache) + } + + + pub fn get_ahash(&self, path: &Path) -> u64 { + hash::get_perceptual_hash(&path, + &hash::Precision::Medium, + &hash::HashType::AHash, + &self.cache) + } + + pub fn get_dhash(&self, path: &Path) -> u64 { + hash::get_perceptual_hash(&path, + &hash::Precision::Medium, + &hash::HashType::DHash, + &self.cache) + } + + pub fn get_phash(&self, path: &Path) -> u64 { + hash::get_perceptual_hash(&path, + &hash::Precision::Medium, + &hash::HashType::DHash, + &self.cache) + } } /** - * Teardown for the library + * Get the Hamming Distance between two hashes. + * Represents the absolute difference between two numbers. */ -#[no_mangle] -pub extern "C" fn teardown() { - match LIB_CACHE.clean() { - Ok(_) => {} - Err(e) => println!("Error: {}", e), - } -} - -pub fn get_phashes(path: &Path) -> hash::PerceptualHashes { - hash::get_perceptual_hashes(path, &hash::Precision::Medium, &LIB_CACHE) -} - - -pub fn get_ahash(path: &Path) -> u64 { - hash::get_perceptual_hash(&path, - &hash::Precision::Medium, - &hash::HashType::AHash, - &LIB_CACHE) -} - -pub fn get_dhash(path: &Path) -> u64 { - hash::get_perceptual_hash(&path, - &hash::Precision::Medium, - &hash::HashType::DHash, - &LIB_CACHE) -} - -pub fn get_phash(path: &Path) -> u64 { - hash::get_perceptual_hash(&path, - &hash::Precision::Medium, - &hash::HashType::DHash, - &LIB_CACHE) -} - pub fn get_hamming_distance(hash1: u64, hash2: u64) -> u64 { hash::calculate_hamming_distance(hash1, hash2) } // External proxies for the get_*hash methods // -#[no_mangle] -pub extern "C" fn ext_get_ahash(path_char: *const libc::c_char) -> libc::uint64_t { - unsafe { - let path_str = CStr::from_ptr(path_char); - let image_path = match path_str.to_str() { - Ok(result) => result, - Err(e) => { - println!("Error: {}. Unable to parse '{}'", - e, - to_hex_string(path_str.to_bytes())); - panic!("Unable to parse path") - } - }; - let path = Path::new(&image_path); - get_ahash(&path) - } -} - -#[no_mangle] -pub extern "C" fn ext_get_dhash(path_char: *const libc::c_char) -> libc::uint64_t { - unsafe { - let path_str = CStr::from_ptr(path_char); - let image_path = match path_str.to_str() { - Ok(result) => result, - Err(e) => { - println!("Error: {}. Unable to parse '{}'", - e, - to_hex_string(path_str.to_bytes())); - panic!("Unable to parse path") - } - }; - let path = Path::new(&image_path); - get_dhash(&path) - } -} - -#[no_mangle] -pub extern "C" fn ext_get_phash(path_char: *const libc::c_char) -> libc::uint64_t { - unsafe { - let path_str = CStr::from_ptr(path_char); - let image_path = match path_str.to_str() { - Ok(result) => result, - Err(e) => { - println!("Error: {}. Unable to parse '{}'", - e, - to_hex_string(path_str.to_bytes())); - panic!("Unable to parse path") - } - }; - let path = Path::new(&image_path); - get_phash(&path) - } -} - -fn to_hex_string(bytes: &[u8]) -> String { - println!("length: {}", bytes.len()); - let mut strs: Vec = Vec::new(); - for byte in bytes { - // println!("{:02x}", byte); - strs.push(format!("{:02x}", byte)); - } - strs.join("\\x") -} +// /** +// * Prepare the library for work. +// * +// * Not performing this step may cause parts to fail. +// */ +// #[no_mangle] +// pub extern "C" fn init() { +// match LIB_CACHE.init() { +// Ok(_) => {} +// Err(e) => println!("Error: {}", e), +// } +// } + +// /** +// * Teardown for the library +// */ +// #[no_mangle] +// pub extern "C" fn teardown() { +// match LIB_CACHE.clean() { +// Ok(_) => {} +// Err(e) => println!("Error: {}", e), +// } +// } + +// #[no_mangle] +// pub extern "C" fn ext_get_ahash(path_char: *const libc::c_char) -> libc::uint64_t { +// unsafe { +// let path_str = CStr::from_ptr(path_char); +// let image_path = match path_str.to_str() { +// Ok(result) => result, +// Err(e) => { +// println!("Error: {}. Unable to parse '{}'", +// e, +// to_hex_string(path_str.to_bytes())); +// panic!("Unable to parse path") +// } +// }; +// let path = Path::new(&image_path); +// get_ahash(&path) +// } +// } + +// #[no_mangle] +// pub extern "C" fn ext_get_dhash(path_char: *const libc::c_char) -> libc::uint64_t { +// unsafe { +// let path_str = CStr::from_ptr(path_char); +// let image_path = match path_str.to_str() { +// Ok(result) => result, +// Err(e) => { +// println!("Error: {}. Unable to parse '{}'", +// e, +// to_hex_string(path_str.to_bytes())); +// panic!("Unable to parse path") +// } +// }; +// let path = Path::new(&image_path); +// get_dhash(&path) +// } +// } + +// #[no_mangle] +// pub extern "C" fn ext_get_phash(path_char: *const libc::c_char) -> libc::uint64_t { +// unsafe { +// let path_str = CStr::from_ptr(path_char); +// let image_path = match path_str.to_str() { +// Ok(result) => result, +// Err(e) => { +// println!("Error: {}. Unable to parse '{}'", +// e, +// to_hex_string(path_str.to_bytes())); +// panic!("Unable to parse path") +// } +// }; +// let path = Path::new(&image_path); +// get_phash(&path) +// } +// } + +// fn to_hex_string(bytes: &[u8]) -> String { +// println!("length: {}", bytes.len()); +// let mut strs: Vec = Vec::new(); +// for byte in bytes { +// // println!("{:02x}", byte); +// strs.push(format!("{:02x}", byte)); +// } +// strs.join("\\x") +// } // Module for the tests // @@ -146,6 +176,10 @@ mod tests { use std::fs; use std::path::Path; use hash; + use cache; + use super::PIHash; + #[cfg(feature = "bench")] + use super::test::Bencher; #[test] fn test_can_get_test_images() { @@ -179,13 +213,13 @@ mod tests { fn test_imageset_hash(hash_type: hash::HashType, hash_precision: hash::Precision, image_paths: [&Path; 3], - image_hashes: [u64; 3]) { + image_hashes: [u64; 3], + lib: &PIHash) { for index in 0..image_paths.len() { let image_path = image_paths[index]; - let calculated_hash = hash::get_perceptual_hash(&image_path, + let calculated_hash = lib.get_perceptual_hash(&image_path, &hash_precision, - &hash_type, - &super::LIB_CACHE); + &hash_type); println!("Image hashes for '{}': expected: {} actual: {}", image_path.to_str().unwrap(), image_hashes[index], @@ -196,8 +230,8 @@ mod tests { #[test] fn test_confirm_ahash_results() { - // Prep_Cache - super::init(); + // Prep_library + let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR)); // Sample_01 tests let sample_01_images: [&Path; 3] = [&Path::new("./test_images/sample_01_large.jpg"), @@ -207,7 +241,8 @@ mod tests { test_imageset_hash(hash::HashType::AHash, hash::Precision::Medium, sample_01_images, - sample_01_hashes); + sample_01_hashes, + &lib); // Sample_02 tests let sample_02_images: [&Path; 3] = [&Path::new("./test_images/sample_02_large.jpg"), @@ -219,7 +254,8 @@ mod tests { test_imageset_hash(hash::HashType::AHash, hash::Precision::Medium, sample_02_images, - sample_02_hashes); + sample_02_hashes, + &lib); // Sample_03 tests let sample_03_images: [&Path; 3] = [&Path::new("./test_images/sample_03_large.jpg"), @@ -231,7 +267,8 @@ mod tests { test_imageset_hash(hash::HashType::AHash, hash::Precision::Medium, sample_03_images, - sample_03_hashes); + sample_03_hashes, + &lib); // Sample_04 tests let sample_04_images: [&Path; 3] = [&Path::new("./test_images/sample_04_large.jpg"), @@ -243,7 +280,8 @@ mod tests { test_imageset_hash(hash::HashType::AHash, hash::Precision::Medium, sample_04_images, - sample_04_hashes); + sample_04_hashes, + &lib); // Clean_Cache // super::teardown(); @@ -251,8 +289,8 @@ mod tests { #[test] fn test_confirm_dhash_results() { - // Prep_Cache - super::init(); + // Prep_library + let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR)); // Sample_01 tests let sample_01_images: [&Path; 3] = [&Path::new("./test_images/sample_01_large.jpg"), @@ -264,7 +302,8 @@ mod tests { test_imageset_hash(hash::HashType::DHash, hash::Precision::Medium, sample_01_images, - sample_01_hashes); + sample_01_hashes, + &lib); // Sample_02 tests let sample_02_images: [&Path; 3] = [&Path::new("./test_images/sample_02_large.jpg"), @@ -276,7 +315,8 @@ mod tests { test_imageset_hash(hash::HashType::DHash, hash::Precision::Medium, sample_02_images, - sample_02_hashes); + sample_02_hashes, + &lib); // Sample_03 tests let sample_03_images: [&Path; 3] = [&Path::new("./test_images/sample_03_large.jpg"), @@ -288,7 +328,8 @@ mod tests { test_imageset_hash(hash::HashType::DHash, hash::Precision::Medium, sample_03_images, - sample_03_hashes); + sample_03_hashes, + &lib); // Sample_04 tests let sample_04_images: [&Path; 3] = [&Path::new("./test_images/sample_04_large.jpg"), @@ -300,7 +341,8 @@ mod tests { test_imageset_hash(hash::HashType::DHash, hash::Precision::Medium, sample_04_images, - sample_04_hashes); + sample_04_hashes, + &lib); // Clean_Cache // super::teardown(); @@ -308,8 +350,8 @@ mod tests { #[test] fn test_confirm_phash_results() { - // Prep_Cache - super::init(); + // Prep_library + let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR)); // Sample_01 tests let sample_01_images: [&Path; 3] = [&Path::new("./test_images/sample_01_large.jpg"), @@ -319,7 +361,8 @@ mod tests { test_imageset_hash(hash::HashType::PHash, hash::Precision::Medium, sample_01_images, - sample_01_hashes); + sample_01_hashes, + &lib); // Sample_02 tests let sample_02_images: [&Path; 3] = [&Path::new("./test_images/sample_02_large.jpg"), @@ -331,7 +374,8 @@ mod tests { test_imageset_hash(hash::HashType::PHash, hash::Precision::Medium, sample_02_images, - sample_02_hashes); + sample_02_hashes, + &lib); // Sample_03 tests let sample_03_images: [&Path; 3] = [&Path::new("./test_images/sample_03_large.jpg"), @@ -343,7 +387,8 @@ mod tests { test_imageset_hash(hash::HashType::PHash, hash::Precision::Medium, sample_03_images, - sample_03_hashes); + sample_03_hashes, + &lib); // Sample_04 tests let sample_04_images: [&Path; 3] = [&Path::new("./test_images/sample_04_large.jpg"), @@ -355,9 +400,75 @@ mod tests { test_imageset_hash(hash::HashType::PHash, hash::Precision::Medium, sample_04_images, - sample_04_hashes); + sample_04_hashes, + &lib); // Clean_Cache // super::teardown(); } + + #[cfg(feature = "bench")] + #[bench] + fn bench_with_cache(bench: &mut Bencher) -> () { + // Prep_library + 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 + lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"),&hash::Precision::Medium, &hash::HashType::PHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"),&hash::Precision::Medium, &hash::HashType::PHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_03_large.jpg"),&hash::Precision::Medium, &hash::HashType::PHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_04_large.jpg"),&hash::Precision::Medium, &hash::HashType::PHash); + + bench.iter(|| { + // Sample_01 Bench + lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"), &hash::Precision::Medium, &hash::HashType::AHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"), &hash::Precision::Medium, &hash::HashType::DHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"), &hash::Precision::Medium, &hash::HashType::PHash); + + // Sample_02 Bench + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"), &hash::Precision::Medium, &hash::HashType::AHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"), &hash::Precision::Medium, &hash::HashType::DHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"), &hash::Precision::Medium, &hash::HashType::PHash); + + // Sample_03 Bench + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"), &hash::Precision::Medium, &hash::HashType::AHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"), &hash::Precision::Medium, &hash::HashType::DHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"), &hash::Precision::Medium, &hash::HashType::PHash); + + // Sample_04 Bench + lib.get_perceptual_hash(&Path::new("./test_images/sample_04_large.jpg"), &hash::Precision::Medium, &hash::HashType::AHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_04_large.jpg"), &hash::Precision::Medium, &hash::HashType::DHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_04_large.jpg"), &hash::Precision::Medium, &hash::HashType::PHash); + }) + } + + #[cfg(feature = "bench")] + #[bench] + fn bench_without_cache(bench: &mut Bencher) -> () { + // Prep_library + 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, &hash::HashType::AHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"), &hash::Precision::Medium, &hash::HashType::DHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"), &hash::Precision::Medium, &hash::HashType::PHash); + + // Sample_02 Bench + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"), &hash::Precision::Medium, &hash::HashType::AHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"), &hash::Precision::Medium, &hash::HashType::DHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"), &hash::Precision::Medium, &hash::HashType::PHash); + + // Sample_03 Bench + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"), &hash::Precision::Medium, &hash::HashType::AHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"), &hash::Precision::Medium, &hash::HashType::DHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_02_large.jpg"), &hash::Precision::Medium, &hash::HashType::PHash); + + // Sample_04 Bench + lib.get_perceptual_hash(&Path::new("./test_images/sample_04_large.jpg"), &hash::Precision::Medium, &hash::HashType::AHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_04_large.jpg"), &hash::Precision::Medium, &hash::HashType::DHash); + lib.get_perceptual_hash(&Path::new("./test_images/sample_04_large.jpg"), &hash::Precision::Medium, &hash::HashType::PHash); + }) + } } diff --git a/src/main.rs b/src/main.rs index 3b18710..842eb3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,16 +54,16 @@ fn main() { } // Init the hashing library - pihash::init(); + let lib = pihash::PIHash::new(Some(pihash::cache::DEFAULT_CACHE_DIR)); // println!("{:?}", args); if args.arg_comparison.len() > 0 { let base_image_path = Path::new(&args.arg_path); - let base_hash = get_requested_perceptual_hashes(&base_image_path, &args); + let base_hash = get_requested_perceptual_hashes(&lib, &base_image_path, &args); let mut comparison_hashes: Vec = Vec::new(); for index in 0..args.arg_comparison.len() { - comparison_hashes.push(get_requested_perceptual_hashes(&Path::new(&args.arg_comparison[index]), &args)); + comparison_hashes.push(get_requested_perceptual_hashes(&lib, &Path::new(&args.arg_comparison[index]), &args)); } let mut similar_images: Vec<&str> = Vec::new(); @@ -82,7 +82,7 @@ fn main() { } else { let image_path = Path::new(&args.arg_path); - let hashes = get_requested_perceptual_hashes(&image_path, &args); + let hashes = get_requested_perceptual_hashes(&lib, &image_path, &args); let hash_result = format!(r#" file: {} ahash: {} @@ -102,21 +102,21 @@ fn flags_get_all_perceptual_hashes(args: &Args) -> bool { (!args.flag_ahash && !args.flag_dhash && !args.flag_phash) } -fn get_requested_perceptual_hashes<'a>(image_path: &'a Path, args: &Args) -> pihash::hash::PerceptualHashes<'a> { +fn get_requested_perceptual_hashes<'a>(lib: &pihash::PIHash, image_path: &'a Path, args: &Args) -> pihash::hash::PerceptualHashes<'a> { let ahash = if args.flag_ahash || flags_get_all_perceptual_hashes(&args) { - pihash::get_ahash(&image_path) + lib.get_ahash(&image_path) } else { 0u64 }; let dhash = if args.flag_dhash || flags_get_all_perceptual_hashes(&args) { - pihash::get_ahash(&image_path) + lib.get_ahash(&image_path) } else { 0u64 }; let phash = if args.flag_phash || flags_get_all_perceptual_hashes(&args) { - pihash::get_ahash(&image_path) + lib.get_ahash(&image_path) } else { 0u64 };