diff --git a/src/hash.rs b/src/hash.rs index 649e545..ab6dea2 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -130,13 +130,9 @@ pub fn prepare_image<'a>(path: &'a Path, hash_type: &HashType, precision: &Preci */ pub fn get_perceptual_hashes<'a>(path: &'a Path, precision: &Precision) -> PerceptualHashes<'a> { let image_path = path.to_str().unwrap(); - let prepared_image = prepare_image(path, &HashType::Ahash, &precision); - // phash uses a DFT, so it needs an image 4 times larger to work with for - // the same precision of hash. That said, this hash is much more accurate. - let phash_prepared_image = prepare_image(path, &HashType::Phash, &precision); let ahash = AHash::new(&path, &precision).get_hash(); - let dhash = get_dhash(&prepared_image); - let phash = get_phash(&phash_prepared_image); + let dhash = DHash::new(&path, &precision).get_hash(); + let phash = PHash::new(&path, &precision).get_hash(); PerceptualHashes { orig_path: &*image_path, ahash: ahash, dhash: dhash, phash: phash } } @@ -177,10 +173,6 @@ impl<'a> PerceptualHash for AHash<'a> { /** * Calculate the ahash of the provided prepared image. * - * # Arguments - * - * * 'prepared_image' - The already prepared image for perceptual processing. - * * # Returns * * A u64 representing the value of the hash @@ -218,49 +210,128 @@ impl<'a> PerceptualHash for AHash<'a> { } } +pub struct DHash<'a> { + prepared_image: Box>, +} -/** - * Calculate the dhash of the provided prepared image - * - * # Arguments - * - * * 'prepared_image' - The already prepared image for perceptual processing - * - * # Return - * - * Returns a u64 representing the value of the hash - */ -pub fn get_dhash(prepared_image: &PreparedImage) -> u64 { - // Stored for later - let first_pixel_val = prepared_image.image.pixels().nth(0).unwrap().channels()[0]; - let last_pixel_val = prepared_image.image.pixels().last().unwrap().channels()[0]; - - // Calculate the dhash - let mut previous_pixel_val = 0u64; - let mut hash = 0u64; - for (index, pixel) in prepared_image.image.pixels().enumerate() { - if index == 0 { - previous_pixel_val = pixel.channels()[0] as u64; - continue; +impl<'a> DHash<'a> { + pub fn new(path: &'a Path, precision: &Precision) -> Self { + DHash { prepared_image: Box::new(prepare_image(&path, &HashType::Dhash, &precision)) } + } +} + +impl<'a> PerceptualHash for DHash<'a> { + /** + * Calculate the dhash of the provided prepared image + * + * # Return + * + * Returns a u64 representing the value of the hash + */ + fn get_hash(&self) -> 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]; + + // 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; } - let channels = pixel.channels(); - let pixel_val = channels[0] as u64; - if pixel_val >= previous_pixel_val { + + if first_pixel_val >= last_pixel_val { hash |= 1; } else { hash |= 0; - } - hash <<= 1; - previous_pixel_val = channels[0] as u64; + } + + hash } +} - if first_pixel_val >= last_pixel_val { - hash |= 1; - } else { - hash |= 0; - } +pub struct PHash<'a> { + prepared_image: Box>, +} - hash +impl<'a> PHash<'a> { + pub fn new(path: &'a Path, precision: &Precision) -> Self { + PHash { prepared_image: Box::new(prepare_image(&path, &HashType::Phash, &precision)) } + } +} + +impl<'a> PerceptualHash for PHash<'a> { + /** + * Calculate the phash of the provided prepared image + * + * # Return + * + * Returns a u64 representing the value of the hash + */ + fn get_hash(&self) -> 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 + let mut data_matrix: Vec> = Vec::new(); + 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); + } + } + + // Perform the 2D DFT operation on our matrix + calculate_2d_dft(&mut data_matrix); + + // 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); + } + hash <<= 1; + } + } + //println!("Hash for {} is {}", prepared_image.orig_path, hash); + hash + } } /* @@ -339,70 +410,6 @@ fn round_float(f: f64) -> f64 { } } -/** - * Calculate the phash of the provided prepared image - * - * # Arguments - * - * * 'prepared_image' - The already prepared image for perceptual processing - * - * # Return - * - * Returns a u64 representing the value of the hash - */ -pub fn get_phash(prepared_image: &PreparedImage) -> u64 { - // Get the image data into a vector to perform the DFT on. - let width = prepared_image.image.width() as usize; - let height = prepared_image.image.height() as usize; - - // Get 2d data to 2d FFT/DFT - let mut data_matrix: Vec> = Vec::new(); - 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); - - // 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); - } - hash <<= 1; - } - } - //println!("Hash for {} is {}", prepared_image.orig_path, hash); - hash -} - #[test] fn test_2d_dft() { let mut test_matrix: Vec> = Vec::new(); diff --git a/src/lib.rs b/src/lib.rs index 4d6c05e..39f7375 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,11 +30,11 @@ pub fn get_ahash(path: &Path) -> u64 { } pub fn get_dhash(path: &Path) -> u64 { - hash::get_dhash(&hash::prepare_image(path, &hash::HashType::Dhash, &hash::Precision::Medium)) + hash::DHash::new(&path, &hash::Precision::Medium).get_hash() } pub fn get_phash(path: &Path) -> u64 { - hash::get_phash(&hash::prepare_image(path, &hash::HashType::Phash, &hash::Precision::Medium)) + hash::PHash::new(&path, &hash::Precision::Medium).get_hash() } pub fn get_hamming_distance(hash1: u64, hash2: u64) -> u64 { @@ -80,12 +80,9 @@ mod tests { // Simple function for the unit tests to succinctly test a set of images // that are organized in the fashion of large->medium->small fn test_imageset_hash u64>( - hash_func: F, - hash_type: hash::HashType, - precision: hash::Precision, - large_path: &str, - medium_path: &str, - small_path: &str, + large_phash: PerceptualHash, + medium_phash: PerceptualHash, + small_phash: PerceptualHash, expected_large_hash: u64, expected_medium_hash: u64, expected_small_hash: u64, @@ -93,13 +90,9 @@ mod tests { expected_large_small_hamming: u64, expected_medium_small_hamming: u64) { - let large_prepared_image = hash::prepare_image(path::Path::new(large_path), &hash_type, &precision); - let medium_prepared_image = hash::prepare_image(path::Path::new(medium_path), &hash_type, &precision); - let small_prepared_image = hash::prepare_image(path::Path::new(small_path), &hash_type, &precision); - - let actual_large_hash = hash_func(&large_prepared_image); - let actual_medium_hash = hash_func(&medium_prepared_image); - let actual_small_hash = hash_func(&small_prepared_image); + let actual_large_hash = large_phash.get_hash(); + let actual_medium_hash = medium_phash.get_hash(); + let actual_small_hash = small_phash.get_hash(); // println for the purpose of debugging println!("{}: expected: {} actual: {}", large_path, expected_large_hash, actual_large_hash); @@ -129,12 +122,9 @@ mod tests { fn confirm_ahash_results() { // Sample_01 tests test_imageset_hash( - hash::get_ahash, - hash::HashType::Ahash, - hash::Precision::Medium, - "./test_images/sample_01_large.jpg", - "./test_images/sample_01_medium.jpg", - "./test_images/sample_01_small.jpg", + hash::AHash::new(path::Path::new("./test_images/sample_01_large.jpg", &hash::Precision::Medium)), + hash::AHash::new(path::Path::new("./test_images/sample_01_medium.jpg", &hash::Precision::Medium)), + hash::AHash::new(path::Path::new("./test_images/sample_01_small.jpg", &hash::Precision::Medium)), 857051991849750, 857051991849750, 857051991849750, @@ -145,12 +135,9 @@ mod tests { // Sample_02 tests test_imageset_hash( - hash::get_ahash, - hash::HashType::Ahash, - hash::Precision::Medium, - "./test_images/sample_02_large.jpg", - "./test_images/sample_02_medium.jpg", - "./test_images/sample_02_small.jpg", + hash::AHash::new(path::Path::new("./test_images/sample_02_large.jpg", &hash::Precision::Medium)), + hash::AHash::new(path::Path::new("./test_images/sample_02_medium.jpg", &hash::Precision::Medium)), + hash::AHash::new(path::Path::new("./test_images/sample_02_small.jpg", &hash::Precision::Medium)), 18446744073441116160, 18446744073441116160, 18446744073441116160, @@ -160,12 +147,9 @@ mod tests { ); // Sample_03 tests test_imageset_hash( - hash::get_ahash, - hash::HashType::Ahash, - hash::Precision::Medium, - "./test_images/sample_03_large.jpg", - "./test_images/sample_03_medium.jpg", - "./test_images/sample_03_small.jpg", + hash::AHash::new(path::Path::new("./test_images/sample_03_large.jpg", &hash::Precision::Medium)), + hash::AHash::new(path::Path::new("./test_images/sample_03_medium.jpg", &hash::Precision::Medium)), + hash::AHash::new(path::Path::new("./test_images/sample_03_small.jpg", &hash::Precision::Medium)), 135670932300497406, 135670932300497406, 135670932300497406, @@ -176,12 +160,9 @@ mod tests { // Sample_04 tests test_imageset_hash( - hash::get_ahash, - hash::HashType::Ahash, - hash::Precision::Medium, - "./test_images/sample_04_large.jpg", - "./test_images/sample_04_medium.jpg", - "./test_images/sample_04_small.jpg", + hash::AHash::new(path::Path::new("./test_images/sample_04_large.jpg", &hash::Precision::Medium)), + hash::AHash::new(path::Path::new("./test_images/sample_04_medium.jpg", &hash::Precision::Medium)), + hash::AHash::new(path::Path::new("./test_images/sample_04_small.jpg", &hash::Precision::Medium)), 18446460933225054208, 18446460933090836480, 18446460933090836480, @@ -195,12 +176,9 @@ mod tests { fn confirm_dhash_results() { // Sample_01 tests test_imageset_hash( - hash::get_dhash, - hash::HashType::Dhash, - hash::Precision::Medium, - "./test_images/sample_01_large.jpg", - "./test_images/sample_01_medium.jpg", - "./test_images/sample_01_small.jpg", + hash::DHash::new(path::Path::new("./test_images/sample_01_large.jpg", &hash::Precision::Medium)), + hash::DHash::new(path::Path::new("./test_images/sample_01_medium.jpg", &hash::Precision::Medium)), + hash::DHash::new(path::Path::new("./test_images/sample_01_small.jpg", &hash::Precision::Medium)), 7937395827556495926, 7937395827556495926, 7939647627370181174, @@ -211,12 +189,9 @@ mod tests { // Sample_02 tests test_imageset_hash( - hash::get_dhash, - hash::HashType::Dhash, - hash::Precision::Medium, - "./test_images/sample_02_large.jpg", - "./test_images/sample_02_medium.jpg", - "./test_images/sample_02_small.jpg", + hash::DHash::new(path::Path::new("./test_images/sample_02_large.jpg", &hash::Precision::Medium)), + hash::DHash::new(path::Path::new("./test_images/sample_02_medium.jpg", &hash::Precision::Medium)), + hash::DHash::new(path::Path::new("./test_images/sample_02_small.jpg", &hash::Precision::Medium)), 11009829669713008949, 11009829670249879861, 11009829669713008949, @@ -226,12 +201,9 @@ mod tests { ); // Sample_03 tests test_imageset_hash( - hash::get_dhash, - hash::HashType::Dhash, - hash::Precision::Medium, - "./test_images/sample_03_large.jpg", - "./test_images/sample_03_medium.jpg", - "./test_images/sample_03_small.jpg", + hash::DHash::new(path::Path::new("./test_images/sample_03_large.jpg", &hash::Precision::Medium)), + hash::DHash::new(path::Path::new("./test_images/sample_03_medium.jpg", &hash::Precision::Medium)), + hash::DHash::new(path::Path::new("./test_images/sample_03_small.jpg", &hash::Precision::Medium)), 225528496439353286, 225528496439353286, 226654396346195908, @@ -242,12 +214,9 @@ mod tests { // Sample_04 tests test_imageset_hash( - hash::get_dhash, - hash::HashType::Dhash, - hash::Precision::Medium, - "./test_images/sample_04_large.jpg", - "./test_images/sample_04_medium.jpg", - "./test_images/sample_04_small.jpg", + hash::DHash::new(path::Path::new("./test_images/sample_04_large.jpg", &hash::Precision::Medium)), + hash::DHash::new(path::Path::new("./test_images/sample_04_medium.jpg", &hash::Precision::Medium)), + hash::DHash::new(path::Path::new("./test_images/sample_04_small.jpg", &hash::Precision::Medium)), 14620651386429567209, 14620651386429567209, 14620651386429567209, @@ -261,12 +230,9 @@ mod tests { fn confirm_phash_results() { // Sample_01 tests test_imageset_hash( - hash::get_phash, - hash::HashType::Phash, - hash::Precision::Medium, - "./test_images/sample_01_large.jpg", - "./test_images/sample_01_medium.jpg", - "./test_images/sample_01_small.jpg", + hash::PHash::new(path::Path::new("./test_images/sample_01_large.jpg", &hash::Precision::Medium)), + hash::PHash::new(path::Path::new("./test_images/sample_01_medium.jpg", &hash::Precision::Medium)), + hash::PHash::new(path::Path::new("./test_images/sample_01_small.jpg", &hash::Precision::Medium)), 72357778504597504, 72357778504597504, 72357778504597504, @@ -277,12 +243,9 @@ mod tests { // Sample_02 tests test_imageset_hash( - hash::get_phash, - hash::HashType::Phash, - hash::Precision::Medium, - "./test_images/sample_02_large.jpg", - "./test_images/sample_02_medium.jpg", - "./test_images/sample_02_small.jpg", + hash::PHash::new(path::Path::new("./test_images/sample_02_large.jpg", &hash::Precision::Medium)), + hash::PHash::new(path::Path::new("./test_images/sample_02_medium.jpg", &hash::Precision::Medium)), + hash::PHash::new(path::Path::new("./test_images/sample_02_small.jpg", &hash::Precision::Medium)), 5332332327550844928, 5332332327550844928, 5332332327550844928, @@ -292,12 +255,9 @@ mod tests { ); // Sample_03 tests test_imageset_hash( - hash::get_phash, - hash::HashType::Phash, - hash::Precision::Medium, - "./test_images/sample_03_large.jpg", - "./test_images/sample_03_medium.jpg", - "./test_images/sample_03_small.jpg", + hash::PHash::new(path::Path::new("./test_images/sample_03_large.jpg", &hash::Precision::Medium)), + hash::PHash::new(path::Path::new("./test_images/sample_03_medium.jpg", &hash::Precision::Medium)), + hash::PHash::new(path::Path::new("./test_images/sample_03_small.jpg", &hash::Precision::Medium)), 6917529027641081856, 6917529027641081856, 6917529027641081856, @@ -308,12 +268,9 @@ mod tests { // Sample_04 tests test_imageset_hash( - hash::get_phash, - hash::HashType::Phash, - hash::Precision::Medium, - "./test_images/sample_04_large.jpg", - "./test_images/sample_04_medium.jpg", - "./test_images/sample_04_small.jpg", + hash::PHash::new(path::Path::new("./test_images/sample_04_large.jpg", &hash::Precision::Medium)), + hash::PHash::new(path::Path::new("./test_images/sample_04_medium.jpg", &hash::Precision::Medium)), + hash::PHash::new(path::Path::new("./test_images/sample_04_small.jpg", &hash::Precision::Medium)), 10997931646002397184, 10997931646002397184, 11142046834078253056,