diff --git a/src/hash.rs b/src/hash.rs new file mode 100644 index 0000000..05247a5 --- /dev/null +++ b/src/hash.rs @@ -0,0 +1,80 @@ +// Copyright 2015 Drew Short . +// +// Licensed under the MIT license. +// This file may not be copied, modified, or distributed except according to those terms. + +// Pull in the image processing crate +extern crate image; + +use std::path::Path; +use self::image::{ + GenericImage, + Pixel +}; + +pub struct PreparedImage<'a> { + orig_path: &'a str, + grey_image: image::ImageBuffer,Vec> +} + +pub fn prepare_image(path: &Path) -> PreparedImage { + let grey_img = image::open(path).unwrap().to_luma(); + let image_path = path.to_str().unwrap(); + PreparedImage { orig_path: &*image_path, grey_image: grey_img } +} + +/* + * Calculate the number of bits different between two hashes + */ +pub fn calculate_hamming_distance(hash1: u64, hash2: u64) -> u64 { + // The binary xor of the two hashes should give us a number representing + // the differences between the two hashes. All that's left is to count + // the number of 1's in the difference to determine the hamming distance + let bin_diff = hash1 ^ hash2; + let bin_diff_str = format!("{:b}", bin_diff); + let mut hamming = 0u64; + for bit in bin_diff_str.chars() { + match bit { + '1' => hamming+=1, + _ => continue + } + } + hamming +} + +/** + * Calculate the ahash of an image provided at the indicated path. + * + * # Arguments + * + * * 'path' - The path to the image file to create a hash from. + * + * Returns a u64 representing the value of the hash + */ +pub fn get_ahash(prepared_image: PreparedImage) -> u64 { + let img = prepared_image.grey_image; + let (width, height) = img.dimensions(); + + // calculating the average pixel value + let mut total = 0u64; + for pixel in img.pixels() { + let channels = pixel.channels(); + total += channels[0] as u64; + } + let mean = total / (width*height) as u64; + + // Calculating a hash based on the mean + let mut hash = 0u64; + for pixel in img.pixels() { + let channels = pixel.channels(); + let pixel_sum = channels[0] as u64; + hash <<= 1; + if pixel_sum >= mean { + hash |= 1; + } else { + hash |= 0; + } + } + + return hash; +} diff --git a/src/lib.rs b/src/lib.rs index 404793c..e792623 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. + +mod hash; + pub fn hello(mut result: String) -> String { let helloworld = "Hello, World!"; result.push_str(helloworld); @@ -18,6 +21,7 @@ mod tests { use super::*; use std::fs; use std::path; + use hash; #[test] fn can_get_test_images() { @@ -29,8 +33,8 @@ mod tests { match ext { Some(_) => { if ext.unwrap() == "jpg" { - println!("Is a image {}: {:?}", num_paths, orig_path) ; num_paths += 1; + println!("Is a image {}: {:?}", num_paths, orig_path) ; } }, _ => { @@ -44,4 +48,63 @@ mod tests { assert!(num_paths == 12); } + // 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_image_set_ahash( + large_path: &str, + medium_path: &str, + small_path: &str, + expected_large_hash: u64, + expected_medium_hash: u64, + expected_small_hash: u64, + expected_large_medium_hamming: u64, + expected_large_small_hamming: u64, + expected_medium_small_hamming: u64) { + + let large_prepared_image = hash::prepare_image(path::Path::new(large_path)); + let medium_prepared_image = hash::prepare_image(path::Path::new(medium_path)); + let small_prepared_image = hash::prepare_image(path::Path::new(small_path)); + + let large_ahash = hash::get_ahash(large_prepared_image); + let medium_ahash = hash::get_ahash(medium_prepared_image); + let small_ahash = hash::get_ahash(small_prepared_image); + + println!("./test_images/sample_01_large.jpg: {}", large_ahash); + println!("./test_images/sample_01_medium.jpg: {}", medium_ahash); + println!("./test_images/sample_01_small.jpg: {}", small_ahash); + + assert!(large_ahash == expected_large_hash); + assert!(medium_ahash == expected_medium_hash); + assert!(small_ahash == expected_small_hash); + + let large_medium_hamming = hash::calculate_hamming_distance(large_ahash, medium_ahash); + let large_small_hamming = hash::calculate_hamming_distance(large_ahash, small_ahash); + let medium_small_hamming = hash::calculate_hamming_distance(medium_ahash, small_ahash); + + println!("Large-Medium Hamming Distance: {}", large_medium_hamming); + println!("Large-Small Hamming Distance: {}", large_small_hamming); + println!("Medium-Small Hamming Distance: {}", medium_small_hamming); + + assert!(large_medium_hamming == expected_large_medium_hamming); + assert!(large_small_hamming == expected_large_small_hamming); + assert!(medium_small_hamming == expected_medium_small_hamming); + + } + + #[test] + fn confirm_ahash_results() { + // Sample_01 tests + test_image_set_ahash( + "./test_images/sample_01_large.jpg", + "./test_images/sample_01_medium.jpg", + "./test_images/sample_01_small.jpg", + 18446744073709551615u64, + 18446744073709551615u64, + 9223372036854775807u64, + 0u64, + 1u64, + 1u64 + ); + } + } diff --git a/test_images/credits.txt b/test_images/credits.txt index 40c61a0..1d0f6c0 100644 --- a/test_images/credits.txt +++ b/test_images/credits.txt @@ -3,9 +3,9 @@ All test images are Creative Commons Licensed This document seeks to credit the original authors where possible # Landscapes -14095127199_e60a64e1b6_h_{large,medium,small}.jpg https://m.flickr.com/#/photos/cubagallery/14095127199/in/search_QM_q_IS_landscape_AND_w_IS_commons -15720949851_c94c8b2d54_h_{large,medium,small}.jpg https://m.flickr.com/#/photos/cubagallery/15720949851/in/search_QM_q_IS_landscape_AND_w_IS_commons -14191645299_7179d4a859_h_{large,medium,small}.jpg https://m.flickr.com/#/photos/cubagallery/14191645299/in/search_QM_q_IS_landscape_AND_w_IS_commons +sample_02_{large,medium,small}.jpg https://m.flickr.com/#/photos/cubagallery/14095127199/in/search_QM_q_IS_landscape_AND_w_IS_commons +sample_03_{large,medium,small}.jpg https://m.flickr.com/#/photos/cubagallery/15720949851/in/search_QM_q_IS_landscape_AND_w_IS_commons +sample_04_{large,medium,small}.jpg https://m.flickr.com/#/photos/cubagallery/14191645299/in/search_QM_q_IS_landscape_AND_w_IS_commons # Faces diff --git a/test_images/14095127199_e60a64e1b6_h_large.jpg b/test_images/sample_02_large.jpg similarity index 100% rename from test_images/14095127199_e60a64e1b6_h_large.jpg rename to test_images/sample_02_large.jpg diff --git a/test_images/14095127199_e60a64e1b6_h_medium.jpg b/test_images/sample_02_medium.jpg similarity index 100% rename from test_images/14095127199_e60a64e1b6_h_medium.jpg rename to test_images/sample_02_medium.jpg diff --git a/test_images/14095127199_e60a64e1b6_h_small.jpg b/test_images/sample_02_small.jpg similarity index 100% rename from test_images/14095127199_e60a64e1b6_h_small.jpg rename to test_images/sample_02_small.jpg diff --git a/test_images/15720949851_c94c8b2d54_h_large.jpg b/test_images/sample_03_large.jpg similarity index 100% rename from test_images/15720949851_c94c8b2d54_h_large.jpg rename to test_images/sample_03_large.jpg diff --git a/test_images/15720949851_c94c8b2d54_h_medium.jpg b/test_images/sample_03_medium.jpg similarity index 100% rename from test_images/15720949851_c94c8b2d54_h_medium.jpg rename to test_images/sample_03_medium.jpg diff --git a/test_images/15720949851_c94c8b2d54_h_small.jpg b/test_images/sample_03_small.jpg similarity index 100% rename from test_images/15720949851_c94c8b2d54_h_small.jpg rename to test_images/sample_03_small.jpg diff --git a/test_images/14191645299_7179d4a859_h_large.jpg b/test_images/sample_04_large.jpg similarity index 100% rename from test_images/14191645299_7179d4a859_h_large.jpg rename to test_images/sample_04_large.jpg diff --git a/test_images/14191645299_7179d4a859_h_medium.jpg b/test_images/sample_04_medium.jpg similarity index 100% rename from test_images/14191645299_7179d4a859_h_medium.jpg rename to test_images/sample_04_medium.jpg diff --git a/test_images/14191645299_7179d4a859_h_small.jpg b/test_images/sample_04_small.jpg similarity index 100% rename from test_images/14191645299_7179d4a859_h_small.jpg rename to test_images/sample_04_small.jpg