You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

188 lines
5.2 KiB

  1. // Copyright 2015 Drew Short <drew@sothr.com>.
  2. //
  3. // Licensed under the MIT license<LICENSE-MIT or http://opensource.org/licenses/MIT>.
  4. // This file may not be copied, modified, or distributed except according to those terms.
  5. // Pull in the image processing crate
  6. extern crate image;
  7. use std::path::Path;
  8. use self::image::{
  9. GenericImage,
  10. Pixel,
  11. FilterType
  12. };
  13. /**
  14. * Prepared image that can be used to generate hashes
  15. */
  16. pub struct PreparedImage<'a> {
  17. orig_path: &'a str,
  18. image: image::ImageBuffer<image::Luma<u8>,Vec<u8>>
  19. }
  20. /**
  21. * Wraps the various perceptual hashes
  22. */
  23. pub struct PerceptualHashes<'a> {
  24. orig_path: &'a str,
  25. ahash: u64,
  26. dhash: u64,
  27. phash: u64
  28. }
  29. /**
  30. * Resonsible for parsing a path, converting an image and package it to be
  31. * hashed.
  32. *
  33. * # Arguments
  34. *
  35. * * 'path' - The path to the image requested to be hashed
  36. * * 'size' - The size that the image should be resize to, in the form of size x size
  37. *
  38. * # Returns
  39. *
  40. * A PreparedImage struct with the required information for performing hashing
  41. *
  42. */
  43. pub fn prepare_image(path: &Path, size: u32) -> PreparedImage {
  44. let image_path = path.to_str().unwrap();
  45. let image = image::open(path).unwrap();
  46. let small_image = image.resize_exact(size, size, FilterType::Lanczos3);
  47. let grey_image = small_image.to_luma();
  48. PreparedImage { orig_path: &*image_path, image: grey_image }
  49. }
  50. /**
  51. * Get all perceptual hashes for an image
  52. */
  53. pub fn get_perceptual_hashes(path: &Path, size: u32, phash_size: u32) -> PerceptualHashes {
  54. let image_path = path.to_str().unwrap();
  55. let prepared_image = prepare_image(path, size);
  56. let phash_prepared_image = prepare_image(path, phash_size);
  57. let ahash = get_ahash(&prepared_image);
  58. let dhash = get_dhash(&prepared_image);
  59. let phash = get_phash(&phash_prepared_image);
  60. PerceptualHashes { orig_path: &*image_path, ahash: ahash, dhash: dhash, phash: phash }
  61. }
  62. /**
  63. * Calculate the number of bits different between two hashes
  64. */
  65. pub fn calculate_hamming_distance(hash1: u64, hash2: u64) -> u64 {
  66. // The binary xor of the two hashes should give us a number representing
  67. // the differences between the two hashes. All that's left is to count
  68. // the number of 1's in the difference to determine the hamming distance
  69. let bin_diff = hash1 ^ hash2;
  70. let bin_diff_str = format!("{:b}", bin_diff);
  71. let mut hamming = 0u64;
  72. for bit in bin_diff_str.chars() {
  73. match bit {
  74. '1' => hamming+=1,
  75. _ => continue
  76. }
  77. }
  78. hamming
  79. }
  80. /**
  81. * Calculate the ahash of the provided prepared image.
  82. *
  83. * # Arguments
  84. *
  85. * * 'prepared_image' - The already prepared image for perceptual processing.
  86. *
  87. * # Returns
  88. *
  89. * A u64 representing the value of the hash
  90. */
  91. pub fn get_ahash(prepared_image: &PreparedImage) -> u64 {
  92. let (width, height) = prepared_image.image.dimensions();
  93. // calculating the average pixel value
  94. let mut total = 0u64;
  95. for pixel in prepared_image.image.pixels() {
  96. let channels = pixel.channels();
  97. //println!("Pixel is: {}", channels[0]);
  98. total += channels[0] as u64;
  99. }
  100. let mean = total / (width*height) as u64;
  101. //println!("Mean for {} is {}", prepared_image.orig_path, mean);
  102. // Calculating a hash based on the mean
  103. let mut hash = 0u64;
  104. for pixel in prepared_image.image.pixels() {
  105. let channels = pixel.channels();
  106. let pixel_sum = channels[0] as u64;
  107. if pixel_sum >= mean {
  108. hash |= 1;
  109. //println!("Pixel {} is >= {} therefore {:b}", pixel_sum, mean, hash);
  110. } else {
  111. hash |= 0;
  112. //println!("Pixel {} is < {} therefore {:b}", pixel_sum, mean, hash);
  113. }
  114. hash <<= 1;
  115. }
  116. //println!("Hash for {} is {}", prepared_image.orig_path, hash);
  117. return hash;
  118. }
  119. /**
  120. * Calculate the dhash of the provided prepared image
  121. *
  122. * # Arguments
  123. *
  124. * * 'prepared_image' - The already prepared image for perceptual processing
  125. *
  126. * # Return
  127. *
  128. * Returns a u64 representing the value of the hash
  129. */
  130. pub fn get_dhash(prepared_image: &PreparedImage) -> u64 {
  131. // Stored for later
  132. let first_pixel_val = prepared_image.image.pixels().nth(0).unwrap().channels()[0];
  133. let last_pixel_val = prepared_image.image.pixels().last().unwrap().channels()[0];
  134. // Calculate the dhash
  135. let mut previous_pixel_val = 0u64;
  136. let mut hash = 0u64;
  137. for (index, pixel) in prepared_image.image.pixels().enumerate() {
  138. if index == 0 {
  139. previous_pixel_val = pixel.channels()[0] as u64;
  140. continue;
  141. }
  142. let channels = pixel.channels();
  143. let pixel_val = channels[0] as u64;
  144. if pixel_val >= previous_pixel_val {
  145. hash |= 1;
  146. } else {
  147. hash |= 0;
  148. }
  149. hash <<= 1;
  150. previous_pixel_val = channels[0] as u64;
  151. }
  152. if first_pixel_val >= last_pixel_val {
  153. hash |= 1;
  154. } else {
  155. hash |= 0;
  156. }
  157. return hash;
  158. }
  159. /**
  160. * Calculate the phash of the provided prepared image
  161. *
  162. * # Arguments
  163. *
  164. * * 'prepared_image' - The already prepared image for perceptual processing
  165. *
  166. * # Return
  167. *
  168. * Returns a u64 representing the value of the hash
  169. */
  170. pub fn get_phash(prepared_image: &PreparedImage) -> u64 {
  171. 0u64
  172. }