From 60f70159377c31cd918eeb652d984abaf54ab0d5 Mon Sep 17 00:00:00 2001 From: Drew Short Date: Mon, 7 Mar 2016 23:07:57 -0600 Subject: [PATCH] Reverted image library. It was no longer processing jpg images. Added some saftey features to opening images. Cleaned up the library calls. Finished the lib conversion of PIHash struct. FFI stuff missing, that will come in another point release to the library. --- Cargo.toml | 4 +- src/hash/ahash.rs | 56 ++++++++++++---------- src/hash/dhash.rs | 58 ++++++++++++----------- src/hash/mod.rs | 42 +++++++++++------ src/hash/phash.rs | 118 ++++++++++++++++++++++++---------------------- src/lib.rs | 3 ++ 6 files changed, 154 insertions(+), 127 deletions(-) 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")]