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.

233 lines
8.5 KiB

8 years ago
8 years ago
8 years ago
  1. // Copyright 2016 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. use super::dft;
  6. use super::dft::Transform;
  7. use super::{HashType, PerceptualHash, Precision, PreparedImage};
  8. use super::prepare_image;
  9. use super::image::{GenericImage, Pixel};
  10. use std::path::Path;
  11. use cache::Cache;
  12. pub struct PHash<'a> {
  13. prepared_image: Box<PreparedImage<'a>>,
  14. }
  15. impl<'a> PHash<'a> {
  16. pub fn new(path: &'a Path, precision: &Precision, cache: &Option<Box<Cache>>) -> Self {
  17. PHash {
  18. prepared_image: Box::new(prepare_image(&path, &HashType::PHash, &precision, cache)),
  19. }
  20. }
  21. }
  22. impl<'a> PerceptualHash for PHash<'a> {
  23. /**
  24. * Calculate the phash of the provided prepared image
  25. *
  26. * # Return
  27. *
  28. * Returns a u64 representing the value of the hash
  29. */
  30. fn get_hash(&self, cache: &Option<Box<Cache>>) -> u64 {
  31. match self.prepared_image.image {
  32. Some(ref image) => {
  33. // Get the image data into a vector to perform the DFT on.
  34. let width = image.width() as usize;
  35. let height = image.height() as usize;
  36. // Get 2d data to 2d FFT/DFT
  37. // Either from the cache or calculate it
  38. // Pretty fast already, so caching doesn't make a huge difference
  39. // Atleast compared to opening and processing the images
  40. let data_matrix: Vec<Vec<f64>> = match *cache {
  41. Some(ref c) => {
  42. match c.get_matrix_from_cache(&Path::new(self.prepared_image.orig_path), width as u32) {
  43. Some(matrix) => matrix,
  44. None => {
  45. let matrix = create_data_matrix(width, height, &image);
  46. // Store this DFT in the cache
  47. match c.put_matrix_in_cache(&Path::new(self.prepared_image.orig_path), width as u32, &matrix) {
  48. Ok(_) => {}
  49. Err(e) => println!("Unable to store matrix in cache. {}", e),
  50. };
  51. matrix
  52. }
  53. }
  54. },
  55. None => create_data_matrix(width, height, &image)
  56. };
  57. // Only need the top left quadrant
  58. let target_width = (width / 4) as usize;
  59. let target_height = (height / 4) as usize;
  60. let dft_width = (width / 4) as f64;
  61. let dft_height = (height / 4) as f64;
  62. // Calculate the mean
  63. let mut total = 0f64;
  64. for x in 0..target_width {
  65. for y in 0..target_height {
  66. total += data_matrix[x][y];
  67. }
  68. }
  69. let mean = total / (dft_width * dft_height);
  70. // Calculating a hash based on the mean
  71. let mut hash = 0u64;
  72. for x in 0..target_width {
  73. // println!("Mean: {} Values: {:?}",mean,data_matrix[x]);
  74. for y in 0..target_height {
  75. if data_matrix[x][y] >= mean {
  76. hash |= 1;
  77. // println!("Pixel {} is >= {} therefore {:b}", pixel_sum, mean, hash);
  78. } else {
  79. hash |= 0;
  80. // println!("Pixel {} is < {} therefore {:b}", pixel_sum, mean, hash);
  81. }
  82. hash <<= 1;
  83. }
  84. }
  85. // println!("Hash for {} is {}", prepared_image.orig_path, hash);
  86. hash
  87. },
  88. None => 0u64
  89. }
  90. }
  91. }
  92. fn create_data_matrix(width: usize, height: usize, image: &super::image::ImageBuffer<super::image::Luma<u8>, Vec<u8>>) -> Vec<Vec<f64>> {
  93. let mut data_matrix: Vec<Vec<f64>> = Vec::new();
  94. // Preparing the results
  95. for x in 0..width {
  96. data_matrix.push(Vec::new());
  97. for y in 0..height {
  98. let pos_x = x as u32;
  99. let pos_y = y as u32;
  100. data_matrix[x]
  101. .push(image
  102. .get_pixel(pos_x, pos_y)
  103. .channels()[0] as f64);
  104. }
  105. }
  106. // Perform the 2D DFT operation on our matrix
  107. calculate_2d_dft(&mut data_matrix);
  108. data_matrix
  109. }
  110. // Use a 1D DFT to cacluate the 2D DFT.
  111. //
  112. // This is achieved by calculating the DFT for each row, then calculating the
  113. // DFT for each column of DFT row data. This means that a 32x32 image with have
  114. // 1024 1D DFT operations performed on it. (Slightly caclulation intensive)
  115. //
  116. // This operation is in place on the data in the provided vector
  117. //
  118. // Inspired by:
  119. // http://www.inf.ufsc.br/~visao/khoros/html-dip/c5/s2/front-page.html
  120. //
  121. // Checked with:
  122. // http://calculator.vhex.net/post/calculator-result/2d-discrete-fourier-transform
  123. //
  124. fn calculate_2d_dft(data_matrix: &mut Vec<Vec<f64>>) {
  125. // println!("{:?}", data_matrix);
  126. let width = data_matrix.len();
  127. let height = data_matrix[0].len();
  128. let mut complex_data_matrix = Vec::with_capacity(width);
  129. // Perform DCT on the columns of data
  130. for x in 0..width {
  131. let mut column: Vec<f64> = Vec::with_capacity(height);
  132. for y in 0..height {
  133. column.push(data_matrix[x][y]);
  134. }
  135. // Perform the DCT on this column
  136. // println!("column[{}] before: {:?}", x, column);
  137. let forward_plan = dft::Plan::new(dft::Operation::Forward, column.len());
  138. column.transform(&forward_plan);
  139. let complex_column = dft::unpack(&column);
  140. // println!("column[{}] after: {:?}", x, complex_column);
  141. complex_data_matrix.push(complex_column);
  142. }
  143. // Perform DCT on the rows of data
  144. for y in 0..height {
  145. let mut row = Vec::with_capacity(width);
  146. for x in 0..width {
  147. row.push(complex_data_matrix[x][y]);
  148. }
  149. // Perform DCT on the row
  150. // println!("row[{}] before: {:?}", y, row);
  151. let forward_plan = dft::Plan::new(dft::Operation::Forward, row.len());
  152. row.transform(&forward_plan);
  153. // println!("row[{}] after: {:?}", y, row);
  154. // Put the row values back
  155. for x in 0..width {
  156. data_matrix[x][y] = round_float(row[x].re);
  157. }
  158. }
  159. }
  160. fn round_float(f: f64) -> f64 {
  161. if f >= super::FLOAT_PRECISION_MAX_1 || f <= super::FLOAT_PRECISION_MIN_1 {
  162. f
  163. } else if f >= super::FLOAT_PRECISION_MAX_2 || f <= super::FLOAT_PRECISION_MIN_2 {
  164. (f * 10_f64).round() / 10_f64
  165. } else if f >= super::FLOAT_PRECISION_MAX_3 || f <= super::FLOAT_PRECISION_MIN_3 {
  166. (f * 100_f64).round() / 100_f64
  167. } else if f >= super::FLOAT_PRECISION_MAX_4 || f <= super::FLOAT_PRECISION_MIN_4 {
  168. (f * 1000_f64).round() / 1000_f64
  169. } else if f >= super::FLOAT_PRECISION_MAX_5 || f <= super::FLOAT_PRECISION_MIN_5 {
  170. (f * 10000_f64).round() / 10000_f64
  171. } else {
  172. (f * 100000_f64).round() / 100000_f64
  173. }
  174. }
  175. #[test]
  176. fn test_2d_dft() {
  177. let mut test_matrix: Vec<Vec<f64>> = Vec::new();
  178. test_matrix.push(vec![1f64, 1f64, 1f64, 3f64]);
  179. test_matrix.push(vec![1f64, 2f64, 2f64, 1f64]);
  180. test_matrix.push(vec![1f64, 2f64, 2f64, 1f64]);
  181. test_matrix.push(vec![3f64, 1f64, 1f64, 1f64]);
  182. println!("{:?}", test_matrix[0]);
  183. println!("{:?}", test_matrix[1]);
  184. println!("{:?}", test_matrix[2]);
  185. println!("{:?}", test_matrix[3]);
  186. println!("Performing 2d DFT");
  187. calculate_2d_dft(&mut test_matrix);
  188. println!("{:?}", test_matrix[0]);
  189. println!("{:?}", test_matrix[1]);
  190. println!("{:?}", test_matrix[2]);
  191. println!("{:?}", test_matrix[3]);
  192. assert!(test_matrix[0][0] == 24_f64);
  193. assert!(test_matrix[0][1] == 0_f64);
  194. assert!(test_matrix[0][2] == 0_f64);
  195. assert!(test_matrix[0][3] == 0_f64);
  196. assert!(test_matrix[1][0] == 0_f64);
  197. assert!(test_matrix[1][1] == 0_f64);
  198. assert!(test_matrix[1][2] == -2_f64);
  199. assert!(test_matrix[1][3] == 2_f64);
  200. assert!(test_matrix[2][0] == 0_f64);
  201. assert!(test_matrix[2][1] == -2_f64);
  202. assert!(test_matrix[2][2] == -4_f64);
  203. assert!(test_matrix[2][3] == -2_f64);
  204. assert!(test_matrix[3][0] == 0_f64);
  205. assert!(test_matrix[3][1] == 2_f64);
  206. assert!(test_matrix[3][2] == -2_f64);
  207. assert!(test_matrix[3][3] == 0_f64);
  208. }