diff --git a/Cargo.toml b/Cargo.toml index d6fc879..235c4d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pihash" -version = "0.3.0" +version = "0.3.1" 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/" @@ -22,7 +22,7 @@ bench = [] [dependencies] docopt = "0.6.78" rustc-serialize = "0.3.18" -image = "0.7.0" +image = "0.6.1" complex = "0.8.0" dft = "0.4.1" sha1 = "0.1.1" diff --git a/src/hash/ahash.rs b/src/hash/ahash.rs index c891baf..3e5deef 100644 --- a/src/hash/ahash.rs +++ b/src/hash/ahash.rs @@ -30,34 +30,38 @@ impl<'a> PerceptualHash for AHash<'a> { * A u64 representing the value of the hash */ fn get_hash(&self, _: &Option>) -> u64 { - let (width, height) = self.prepared_image.image.dimensions(); + match self.prepared_image.image { + Some(ref image) => { + let (width, height) = image.dimensions(); - // calculating the average pixel value - let mut total = 0u64; - for pixel in self.prepared_image.image.pixels() { - let channels = pixel.channels(); - // println!("Pixel is: {}", channels[0]); - total += channels[0] as u64; - } - let mean = total / (width * height) as u64; - // println!("Mean for {} is {}", prepared_image.orig_path, mean); + // calculating the average pixel value + let mut total = 0u64; + for pixel in image.pixels() { + let channels = pixel.channels(); + // println!("Pixel is: {}", channels[0]); + total += channels[0] as u64; + } + let mean = total / (width * height) as u64; + // println!("Mean for {} is {}", prepared_image.orig_path, mean); - // Calculating a hash based on the mean - let mut hash = 0u64; - for pixel in self.prepared_image.image.pixels() { - let channels = pixel.channels(); - let pixel_sum = channels[0] as u64; - if pixel_sum >= 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; + // Calculating a hash based on the mean + let mut hash = 0u64; + for pixel in image.pixels() { + let channels = pixel.channels(); + let pixel_sum = channels[0] as u64; + if pixel_sum >= 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 } - // println!("Hash for {} is {}", prepared_image.orig_path, hash); - - hash } } diff --git a/src/hash/dhash.rs b/src/hash/dhash.rs index 651b4fc..415e773 100644 --- a/src/hash/dhash.rs +++ b/src/hash/dhash.rs @@ -30,35 +30,39 @@ impl<'a> PerceptualHash for DHash<'a> { * Returns a u64 representing the value of the hash */ 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]; + match self.prepared_image.image { + Some(ref image) => { + let first_pixel_val = image.pixels().nth(0).unwrap().channels()[0]; + let last_pixel_val = image.pixels().last().unwrap().channels()[0]; - // Calculate the dhash - let mut previous_pixel_val = 0u64; - let mut hash = 0u64; - for (index, pixel) in self.prepared_image.image.pixels().enumerate() { - if index == 0 { - previous_pixel_val = pixel.channels()[0] as u64; - continue; - } - let channels = pixel.channels(); - let pixel_val = channels[0] as u64; - if pixel_val >= previous_pixel_val { - hash |= 1; - } else { - hash |= 0; - } - hash <<= 1; - previous_pixel_val = channels[0] as u64; - } + // Calculate the dhash + let mut previous_pixel_val = 0u64; + let mut hash = 0u64; + for (index, pixel) in image.pixels().enumerate() { + if index == 0 { + previous_pixel_val = pixel.channels()[0] as u64; + continue; + } + let channels = pixel.channels(); + let pixel_val = channels[0] as u64; + if pixel_val >= previous_pixel_val { + hash |= 1; + } else { + hash |= 0; + } + hash <<= 1; + previous_pixel_val = channels[0] as u64; + } - if first_pixel_val >= last_pixel_val { - hash |= 1; - } else { - hash |= 0; - } + if first_pixel_val >= last_pixel_val { + hash |= 1; + } else { + hash |= 0; + } - hash + hash + }, + None => 0u64 + } } } diff --git a/src/hash/mod.rs b/src/hash/mod.rs index 639ee6a..da3caaf 100644 --- a/src/hash/mod.rs +++ b/src/hash/mod.rs @@ -43,7 +43,7 @@ const HAMMING_DISTANCE_SIMILARITY_LIMIT: u64 = 5u64; */ pub struct PreparedImage<'a> { orig_path: &'a str, - image: image::ImageBuffer, Vec>, + image: Option, Vec>>, } /** @@ -143,37 +143,49 @@ pub fn prepare_image<'a>(path: &'a Path, Some(image) => { PreparedImage { orig_path: &*image_path, - image: image, + image: Some(image), } } None => { - let image = process_image(&path, size); + let processed_image = process_image(&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), + 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 => {} }; - image + processed_image } } }, - None => process_image(&path, size), + None => process_image(&image_path, size), } } /** * Turn the image into something we can work with */ -fn process_image(path: &Path, - size: u32) -> PreparedImage{ +fn process_image<'a>(image_path: &'a str, + size: u32) -> PreparedImage<'a> { // 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(); + //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) => { + println!("Error Processing Image [{}]: {} ", image_path, e); + None + } + }; PreparedImage { orig_path: &*image_path, - image: grey_image, + image: image, } } diff --git a/src/hash/phash.rs b/src/hash/phash.rs index 9e85b3b..19e8f17 100644 --- a/src/hash/phash.rs +++ b/src/hash/phash.rs @@ -32,68 +32,73 @@ impl<'a> PerceptualHash for PHash<'a> { * Returns a u64 representing the value of the hash */ 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; - - // 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 - 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 + match self.prepared_image.image { + Some(ref image) => { + // Get the image data into a vector to perform the DFT on. + let width = image.width() as usize; + let height = image.height() as usize; + + // 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 + 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, &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 + } + } + }, + None => create_data_matrix(width, height, &image) + }; + + // Only need the top left quadrant + let target_width = (width / 4) as usize; + let target_height = (height / 4) as usize; + let dft_width = (width / 4) as f64; + let dft_height = (height / 4) as f64; + + // Calculate the mean + let mut total = 0f64; + for x in 0..target_width { + for y in 0..target_height { + total += data_matrix[x][y]; } } - }, - None => create_data_matrix(width, height, &self.prepared_image) - }; - - // Only need the top left quadrant - let target_width = (width / 4) as usize; - let target_height = (height / 4) as usize; - let dft_width = (width / 4) as f64; - let dft_height = (height / 4) as f64; - - // Calculate the mean - let mut total = 0f64; - for x in 0..target_width { - for y in 0..target_height { - total += data_matrix[x][y]; - } - } - let mean = total / (dft_width * dft_height); - - // 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); + let mean = total / (dft_width * dft_height); + + // 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; + } } - hash <<= 1; - } + // println!("Hash for {} is {}", prepared_image.orig_path, hash); + hash + }, + None => 0u64 } - // println!("Hash for {} is {}", prepared_image.orig_path, hash); - hash } } -fn create_data_matrix(width: usize, height: usize, prepared_image: &PreparedImage) -> 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 { @@ -102,8 +107,7 @@ fn create_data_matrix(width: usize, height: usize, prepared_image: &PreparedImag let pos_x = x as u32; let pos_y = y as u32; data_matrix[x] - .push(prepared_image - .image + .push(image .get_pixel(pos_x, pos_y) .channels()[0] as f64); } diff --git a/src/lib.rs b/src/lib.rs index 81e5dcb..723b38f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,9 @@ // Licensed under the MIT license. // This file may not be copied, modified, or distributed except according to those terms. +// Enable nightly features for extra testing behind the bench feature +#![cfg_attr(feature = "bench", feature(test))] + extern crate libc; extern crate rustc_serialize; #[cfg(feature = "bench")]