From 88eac9cb29cddfd094d434a62390bfb4768b2746 Mon Sep 17 00:00:00 2001 From: Drew Short Date: Thu, 13 Apr 2017 03:04:02 +0000 Subject: [PATCH 1/3] Resolving test compilation issues --- src/lib.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 570d470..f82490d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -204,8 +204,8 @@ mod tests { } // println!("Name: {}", path.unwrap().path().display()) } - // Currently 12 images in the test imaages directory - assert!(num_paths == 12); + // Currently 12 images in the test images directory + assert_eq!(num_paths, 12); } /** @@ -228,7 +228,7 @@ mod tests { image_path.to_str().unwrap(), image_hashes[index], calculated_hash); - assert!(calculated_hash == image_hashes[index]); + assert_eq!(calculated_hash,image_hashes[index]); hashes[index] = calculated_hash; } @@ -258,7 +258,9 @@ mod tests { 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, 857051992374038]; + let sample_01_hashes: [u64; 3] = [857051991849750, + 857051991849750, + 857051992374038]; test_imageset_hash(hash::HashType::AHash, hash::Precision::Medium, 1u64, @@ -386,7 +388,9 @@ mod tests { 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]; + let sample_01_hashes: [u64; 3] = [72357778504597504, + 72357778504597504, + 72357778504597504]; test_imageset_hash(hash::HashType::PHash, hash::Precision::Medium, 0u64, From af5ea1ac0fb11dd32844320abf6805a680a79987 Mon Sep 17 00:00:00 2001 From: Drew Short Date: Sun, 14 May 2017 15:22:21 -0500 Subject: [PATCH 2/3] Updating dependencies. Incrementing patch version. --- Cargo.toml | 18 +++++++++--------- src/cache.rs | 3 ++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0c74e27..2c4f162 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pihash" -version = "0.3.4" +version = "0.3.5" 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/" @@ -20,11 +20,11 @@ default = [] bench = [] [dependencies] -libc = "0.2.10" -rustc-serialize = "0.3.19" -dft = "0.4.3" -image = "0.9.0" -num = "0.1.32" -docopt = "0.6.80" -flate2 = "0.2.13" -sha1 = "0.1.1" \ No newline at end of file +libc = "0.2.20" +rustc-serialize = "0.3.22" +dft = "0.5.4" +image = "0.13.0" +num = "0.1.36" +docopt = "0.7.0" +flate2 = "0.2.19" +sha1 = "0.2.0" diff --git a/src/cache.rs b/src/cache.rs index cb93a15..9f5c2a8 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -139,8 +139,9 @@ impl<'a> Cache<'a> { try!(source.read_to_end(&mut buf)); let mut sha1 = Sha1::new(); sha1.update(&buf); + let digest = sha1.digest(); // Return the hex result of the hash - Ok(sha1.hexdigest()) + Ok(format!("{}", digest)) } /** From 487401a45af64f457b8859396fc9ac7fa37877db Mon Sep 17 00:00:00 2001 From: Drew Short Date: Sun, 14 May 2017 15:45:51 -0500 Subject: [PATCH 3/3] Rustfmt and code cleanup --- src/cache.rs | 29 +++---- src/hash/ahash.rs | 4 +- src/hash/dhash.rs | 4 +- src/hash/mod.rs | 24 +++--- src/hash/phash.rs | 29 ++++--- src/lib.rs | 198 ++++++++++++++++++++++++---------------------- src/main.rs | 66 +++++++++------- 7 files changed, 183 insertions(+), 171 deletions(-) diff --git a/src/cache.rs b/src/cache.rs index 9f5c2a8..66b10e4 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -253,11 +253,10 @@ impl<'a> Cache<'a> { let mut compressor = ZlibEncoder::new(Vec::new(), Compression::Default); for row in file_contents { - let mut row_str = row.iter().fold(String::new(), - |acc, &item| { - acc + - &format!("{},", item) - }); + let mut row_str = + row.iter() + .fold(String::new(), + |acc, &item| acc + &format!("{},", item)); // remove the last comma let desire_len = row_str.len() - 1; row_str.truncate(desire_len); @@ -319,17 +318,15 @@ impl<'a> Cache<'a> { } }; // convert the matrix - let matrix: Vec> = matrix_data_str.trim() - .split("\n") - .map(|line| { - line.split(",") - .map(|f| { - f64::from_str(f) - .unwrap() - }) - .collect() - }) - .collect(); + let matrix: Vec> = matrix_data_str + .trim() + .split("\n") + .map(|line| { + line.split(",") + .map(|f| f64::from_str(f).unwrap()) + .collect() + }) + .collect(); Some(matrix) } diff --git a/src/hash/ahash.rs b/src/hash/ahash.rs index 06db899..6e32cdc 100644 --- a/src/hash/ahash.rs +++ b/src/hash/ahash.rs @@ -60,8 +60,8 @@ impl<'a> PerceptualHash for AHash<'a> { } // println!("Hash for {} is {}", prepared_image.orig_path, hash); hash - }, - None => 0u64 + } + None => 0u64, } } } diff --git a/src/hash/dhash.rs b/src/hash/dhash.rs index bc73b70..d5748d8 100644 --- a/src/hash/dhash.rs +++ b/src/hash/dhash.rs @@ -61,8 +61,8 @@ impl<'a> PerceptualHash for DHash<'a> { } hash - }, - None => 0u64 + } + None => 0u64, } } } diff --git a/src/hash/mod.rs b/src/hash/mod.rs index dd9e71e..e5e1fd1 100644 --- a/src/hash/mod.rs +++ b/src/hash/mod.rs @@ -59,10 +59,13 @@ pub struct PerceptualHashes<'a> { 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 + 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 } @@ -146,7 +149,7 @@ pub fn prepare_image<'a>(path: &'a Path, image: Some(image), } } - None => { + None => { let processed_image = process_image(&image_path, size); // Oh, and save it in a cache match processed_image.image { @@ -155,13 +158,13 @@ pub fn prepare_image<'a>(path: &'a Path, Ok(_) => {} Err(e) => println!("Unable to store image in cache. {}", e), }; - }, + } None => {} }; processed_image } } - }, + } None => process_image(&image_path, size), } } @@ -169,16 +172,15 @@ pub fn prepare_image<'a>(path: &'a Path, /** * Turn the image into something we can work with */ -fn process_image<'a>(image_path: &'a str, - size: u32) -> PreparedImage<'a> { +fn process_image<'a>(image_path: &'a str, size: u32) -> PreparedImage<'a> { // Otherwise let's do that work now and store it. //println!("Path: {}", image_path); let image = match image::open(Path::new(image_path)) { Ok(image) => { let small_image = image.resize_exact(size, size, FilterType::Lanczos3); Some(small_image.to_luma()) - }, - Err(e) => { + } + Err(e) => { println!("Error Processing Image [{}]: {} ", image_path, e); None } diff --git a/src/hash/phash.rs b/src/hash/phash.rs index 5e12c43..d1d2774 100644 --- a/src/hash/phash.rs +++ b/src/hash/phash.rs @@ -44,20 +44,23 @@ impl<'a> PerceptualHash for PHash<'a> { // Atleast 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), width as u32) { + 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, &image); // Store this DFT in the cache - match c.put_matrix_in_cache(&Path::new(self.prepared_image.orig_path), width as u32, &matrix) { + 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 } } - }, - None => create_data_matrix(width, height, &image) + } + None => create_data_matrix(width, height, &image), }; // Only need the top left quadrant @@ -78,27 +81,26 @@ impl<'a> PerceptualHash for PHash<'a> { // Calculating a hash based on the mean let mut hash = 0u64; for x in 0..target_width { - // println!("Mean: {} Values: {:?}",mean,data_matrix[x]); for y in 0..target_height { if data_matrix[x][y] >= mean { hash |= 1; - // println!("Pixel {} is >= {} therefore {:b}", pixel_sum, mean, hash); } else { hash |= 0; - // println!("Pixel {} is < {} therefore {:b}", pixel_sum, mean, hash); } hash <<= 1; } } - // println!("Hash for {} is {}", prepared_image.orig_path, hash); hash - }, - None => 0u64 + } + None => 0u64, } } } -fn create_data_matrix(width: usize, height: usize, image: &super::image::ImageBuffer, Vec>) -> Vec> { +fn create_data_matrix(width: usize, + height: usize, + image: &super::image::ImageBuffer, Vec>) + -> Vec> { let mut data_matrix: Vec> = Vec::new(); // Preparing the results for x in 0..width { @@ -106,10 +108,7 @@ fn create_data_matrix(width: usize, height: usize, image: &super::image::ImageBu for y in 0..height { let pos_x = x as u32; let pos_y = y as u32; - data_matrix[x] - .push(image - .get_pixel(pos_x, pos_y) - .channels()[0] as f64); + data_matrix[x].push(image.get_pixel(pos_x, pos_y).channels()[0] as f64); } } diff --git a/src/lib.rs b/src/lib.rs index f82490d..128dcc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,58 +20,66 @@ use cache::Cache; #[repr(C)] pub struct PIHash<'a> { - cache: Option> + cache: Option>, } 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(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::PHash, - &self.cache) - } + /** + * 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(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::PHash, + &self.cache) + } } /** @@ -86,24 +94,24 @@ pub fn get_hamming_distance(hash1: u64, hash2: u64) -> u64 { #[no_mangle] pub extern "C" fn ext_init(cache_path_char: *const libc::c_char) -> *const libc::c_void { - unsafe { + unsafe { let path_cstr = CStr::from_ptr(cache_path_char); let path_str = match path_cstr.to_str() { - Ok(path) => Some(path), - Err(_) => None, + Ok(path) => Some(path), + Err(_) => None, }; //println!("Created new lib, with cache at {}", path_str.unwrap()); let lib = Box::new(PIHash::new(path_str)); let ptr = Box::into_raw(lib) as *mut libc::c_void; ptr - } + } } #[no_mangle] pub extern "C" fn ext_free(raw_lib: *const libc::c_void) { - unsafe { - drop(Box::from_raw(raw_lib as *mut PIHash)); - } + unsafe { + drop(Box::from_raw(raw_lib as *mut PIHash)); + } } #[no_mangle] @@ -172,7 +180,7 @@ fn to_hex_string(bytes: &[u8]) -> String { // Module for the tests // - #[cfg(test)] +#[cfg(test)] mod tests { use std::fs; @@ -194,11 +202,11 @@ mod tests { Some(_) => { if ext.unwrap() == "jpg" { num_paths += 1; - println!("Is a image {}: {:?}", num_paths, orig_path) ; + println!("Is a image {}: {:?}", num_paths, orig_path); } } _ => { - println!("Not an image: {:?}", orig_path) ; + println!("Not an image: {:?}", orig_path); continue; } } @@ -221,31 +229,29 @@ mod tests { let mut hashes: [u64; 3] = [0; 3]; for index in 0..image_paths.len() { let image_path = image_paths[index]; - let calculated_hash = lib.get_perceptual_hash(&image_path, - &hash_precision, - &hash_type); + let calculated_hash = lib.get_perceptual_hash(&image_path, &hash_precision, &hash_type); println!("Image hashes for [{}] expected: [{}] actual: [{}]", image_path.to_str().unwrap(), image_hashes[index], calculated_hash); - assert_eq!(calculated_hash,image_hashes[index]); + assert_eq!(calculated_hash, image_hashes[index]); hashes[index] = calculated_hash; } 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); + 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); + } } - } } } @@ -258,9 +264,7 @@ mod tests { 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, - 857051992374038]; + let sample_01_hashes: [u64; 3] = [857051991849750, 857051991849750, 857051992374038]; test_imageset_hash(hash::HashType::AHash, hash::Precision::Medium, 1u64, @@ -286,9 +290,8 @@ mod tests { let sample_03_images: [&Path; 3] = [&Path::new("./test_images/sample_03_large.jpg"), &Path::new("./test_images/sample_03_medium.jpg"), &Path::new("./test_images/sample_03_small.jpg")]; - let sample_03_hashes: [u64; 3] = [135670932300497406, - 135670932300497406, - 135670932300497406]; + let sample_03_hashes: [u64; 3] = + [135670932300497406, 135670932300497406, 135670932300497406]; test_imageset_hash(hash::HashType::AHash, hash::Precision::Medium, 1u64, @@ -351,9 +354,8 @@ mod tests { let sample_03_images: [&Path; 3] = [&Path::new("./test_images/sample_03_large.jpg"), &Path::new("./test_images/sample_03_medium.jpg"), &Path::new("./test_images/sample_03_small.jpg")]; - let sample_03_hashes: [u64; 3] = [262683193365159876, - 225528496439353284, - 225528496435158982]; + let sample_03_hashes: [u64; 3] = + [262683193365159876, 225528496439353284, 225528496435158982]; test_imageset_hash(hash::HashType::DHash, hash::Precision::Medium, 4u64, @@ -388,9 +390,7 @@ mod tests { 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]; + let sample_01_hashes: [u64; 3] = [72357778504597504, 72357778504597504, 72357778504597504]; test_imageset_hash(hash::HashType::PHash, hash::Precision::Medium, 0u64, @@ -447,28 +447,34 @@ mod tests { #[cfg(feature = "bench")] #[bench] fn bench_with_cache(bench: &mut Bencher) -> () { - // Prep_library + // 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_01_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::PHash); - }) + // Sample_01 Bench + lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"), + &hash::Precision::Medium, + &hash::HashType::PHash); + }) } #[cfg(feature = "bench")] #[bench] fn bench_without_cache(bench: &mut Bencher) -> () { - // Prep_library + // 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::PHash); - }) + // Sample_01 Bench + lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"), + &hash::Precision::Medium, + &hash::HashType::PHash); + }) } } diff --git a/src/main.rs b/src/main.rs index 842eb3b..23c0386 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,8 +44,8 @@ struct Args { fn main() { let args: Args = Docopt::new(USAGE) - .and_then(|d| d.decode()) - .unwrap_or_else(|e| e.exit()); + .and_then(|d| d.decode()) + .unwrap_or_else(|e| e.exit()); // Print version information and exit if args.flag_version { @@ -63,7 +63,10 @@ fn main() { let mut comparison_hashes: Vec = Vec::new(); for index in 0..args.arg_comparison.len() { - comparison_hashes.push(get_requested_perceptual_hashes(&lib, &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(); @@ -93,36 +96,41 @@ fn main() { hashes.ahash, hashes.dhash, hashes.phash); - println!("{}", hash_result); + println!("{}", hash_result); } } fn flags_get_all_perceptual_hashes(args: &Args) -> bool { (args.flag_ahash && args.flag_dhash && args.flag_phash) || - (!args.flag_ahash && !args.flag_dhash && !args.flag_phash) + (!args.flag_ahash && !args.flag_dhash && !args.flag_phash) } -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) { - lib.get_ahash(&image_path) - } else { - 0u64 - }; - - let dhash = if args.flag_dhash || flags_get_all_perceptual_hashes(&args) { - lib.get_ahash(&image_path) - } else { - 0u64 - }; - - let phash = if args.flag_phash || flags_get_all_perceptual_hashes(&args) { - lib.get_ahash(&image_path) - } else { - 0u64 - }; - - pihash::hash::PerceptualHashes {orig_path: image_path.to_str().unwrap(), - ahash: ahash, - dhash: dhash, - phash: phash} -} \ No newline at end of file +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) { + lib.get_ahash(&image_path) + } else { + 0u64 + }; + + let dhash = if args.flag_dhash || flags_get_all_perceptual_hashes(&args) { + lib.get_ahash(&image_path) + } else { + 0u64 + }; + + let phash = if args.flag_phash || flags_get_all_perceptual_hashes(&args) { + lib.get_ahash(&image_path) + } else { + 0u64 + }; + + pihash::hash::PerceptualHashes { + orig_path: image_path.to_str().unwrap(), + ahash: ahash, + dhash: dhash, + phash: phash, + } +}