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.

503 lines
19 KiB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
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. // Enable nightly features for extra testing behind the bench feature
  6. #![cfg_attr(feature = "bench", feature(test))]
  7. extern crate libc;
  8. extern crate rustc_serialize;
  9. #[cfg(feature = "bench")]
  10. extern crate test;
  11. pub mod hash;
  12. pub mod cache;
  13. use std::path::Path;
  14. use std::ffi::CStr;
  15. use cache::Cache;
  16. #[repr(C)]
  17. pub struct PIHash<'a> {
  18. cache: Option<Cache<'a>>,
  19. }
  20. impl<'a> PIHash<'a> {
  21. /**
  22. * Create a new pihash library, and initialize a cache of a path is passed.
  23. * If none is passed then no cache is initialized or used with the library
  24. */
  25. pub fn new(cache_path: Option<&'a str>) -> PIHash<'a> {
  26. match cache_path {
  27. Some(path) => {
  28. let cache = Cache {
  29. cache_dir: path,
  30. use_cache: true,
  31. };
  32. match cache.init() {
  33. Ok(_) => PIHash { cache: Some(cache) },
  34. Err(e) => {
  35. println!("Error creating library with cache: {}", e);
  36. PIHash { cache: None }
  37. }
  38. }
  39. }
  40. None => PIHash { cache: None },
  41. }
  42. }
  43. pub fn get_perceptual_hash(&self,
  44. path: &Path,
  45. precision: &hash::Precision,
  46. hash_type: &hash::HashType)
  47. -> u64 {
  48. hash::get_perceptual_hash(&path, &precision, &hash_type, &self.cache)
  49. }
  50. pub fn get_phashes(&self, path: &'a Path) -> hash::PerceptualHashes {
  51. hash::get_perceptual_hashes(&path,
  52. &hash::Precision::Medium,
  53. &self.cache)
  54. }
  55. pub fn get_ahash(&self, path: &Path) -> u64 {
  56. hash::get_perceptual_hash(&path,
  57. &hash::Precision::Medium,
  58. &hash::HashType::AHash,
  59. &self.cache)
  60. }
  61. pub fn get_dhash(&self, path: &Path) -> u64 {
  62. hash::get_perceptual_hash(&path,
  63. &hash::Precision::Medium,
  64. &hash::HashType::DHash,
  65. &self.cache)
  66. }
  67. pub fn get_phash(&self, path: &Path) -> u64 {
  68. hash::get_perceptual_hash(&path,
  69. &hash::Precision::Medium,
  70. &hash::HashType::PHash,
  71. &self.cache)
  72. }
  73. }
  74. /**
  75. * Get the Hamming Distance between two hashes.
  76. * Represents the absolute difference between two numbers.
  77. */
  78. pub fn get_hamming_distance(hash1: u64, hash2: u64) -> u64 {
  79. hash::calculate_hamming_distance(hash1, hash2)
  80. }
  81. // External proxies for the get_*hash methods //
  82. #[no_mangle]
  83. pub extern "C" fn ext_init(cache_path_char: *const libc::c_char) -> *const libc::c_void {
  84. unsafe {
  85. let path_cstr = CStr::from_ptr(cache_path_char);
  86. let path_str = match path_cstr.to_str() {
  87. Ok(path) => Some(path),
  88. Err(_) => None,
  89. };
  90. // println!("Created new lib, with cache at {}", path_str.unwrap());
  91. let lib = Box::new(PIHash::new(path_str));
  92. let ptr = Box::into_raw(lib) as *mut libc::c_void;
  93. ptr
  94. }
  95. }
  96. #[no_mangle]
  97. pub extern "C" fn ext_free(raw_lib: *const libc::c_void) {
  98. unsafe {
  99. drop(Box::from_raw(raw_lib as *mut PIHash));
  100. }
  101. }
  102. #[no_mangle]
  103. pub extern "C" fn ext_get_ahash(lib: &PIHash, path_char: *const libc::c_char) -> libc::uint64_t {
  104. unsafe {
  105. let path_str = CStr::from_ptr(path_char);
  106. let image_path = match path_str.to_str() {
  107. Ok(result) => result,
  108. Err(e) => {
  109. println!("Error: {}. Unable to parse '{}'",
  110. e,
  111. to_hex_string(path_str.to_bytes()));
  112. panic!("Unable to parse path")
  113. }
  114. };
  115. let path = Path::new(&image_path);
  116. lib.get_ahash(path)
  117. }
  118. }
  119. #[no_mangle]
  120. pub extern "C" fn ext_get_dhash(lib: &PIHash, path_char: *const libc::c_char) -> libc::uint64_t {
  121. unsafe {
  122. let path_str = CStr::from_ptr(path_char);
  123. let image_path = match path_str.to_str() {
  124. Ok(result) => result,
  125. Err(e) => {
  126. println!("Error: {}. Unable to parse '{}'",
  127. e,
  128. to_hex_string(path_str.to_bytes()));
  129. panic!("Unable to parse path")
  130. }
  131. };
  132. let path = Path::new(&image_path);
  133. lib.get_dhash(path)
  134. }
  135. }
  136. #[no_mangle]
  137. pub extern "C" fn ext_get_phash(lib: &PIHash, path_char: *const libc::c_char) -> libc::uint64_t {
  138. unsafe {
  139. let path_str = CStr::from_ptr(path_char);
  140. let image_path = match path_str.to_str() {
  141. Ok(result) => result,
  142. Err(e) => {
  143. println!("Error: {}. Unable to parse '{}'",
  144. e,
  145. to_hex_string(path_str.to_bytes()));
  146. panic!("Unable to parse path")
  147. }
  148. };
  149. let path = Path::new(&image_path);
  150. lib.get_phash(path)
  151. }
  152. }
  153. #[repr(C)]
  154. pub struct PIHashes {
  155. ahash: libc::uint64_t,
  156. dhash: libc::uint64_t,
  157. phash: libc::uint64_t
  158. }
  159. #[no_mangle]
  160. pub extern "C" fn ext_get_phashes(lib: &PIHash, path_char: *const libc::c_char) -> * mut PIHashes {
  161. unsafe {
  162. let path_str = CStr::from_ptr(path_char);
  163. let image_path = match path_str.to_str() {
  164. Ok(result) => result,
  165. Err(e) => {
  166. println!("Error: {}. Unable to parse '{}'",
  167. e,
  168. to_hex_string(path_str.to_bytes()));
  169. panic!("Unable to parse path")
  170. }
  171. };
  172. let path = Path::new(&image_path);
  173. let phashes = lib.get_phashes(path);
  174. Box::into_raw(Box::new(PIHashes {
  175. ahash: phashes.ahash,
  176. dhash: phashes.dhash,
  177. phash: phashes.phash
  178. }))
  179. }
  180. }
  181. #[no_mangle]
  182. pub extern "C" fn ext_free_phashes(raw_phashes: *const libc::c_void) {
  183. unsafe {
  184. drop(Box::from_raw(raw_phashes as *mut PIHashes));
  185. }
  186. }
  187. fn to_hex_string(bytes: &[u8]) -> String {
  188. println!("length: {}", bytes.len());
  189. let mut strs: Vec<String> = Vec::new();
  190. for byte in bytes {
  191. // println!("{:02x}", byte);
  192. strs.push(format!("{:02x}", byte));
  193. }
  194. strs.join("\\x")
  195. }
  196. // Module for the tests
  197. //
  198. #[cfg(test)]
  199. mod tests {
  200. use std::fs;
  201. use std::path::Path;
  202. use hash;
  203. use cache;
  204. use super::PIHash;
  205. #[cfg(feature = "bench")]
  206. use super::test::Bencher;
  207. #[test]
  208. fn test_can_get_test_images() {
  209. let paths = fs::read_dir(&Path::new("./test_images")).unwrap();
  210. let mut num_paths = 0;
  211. for path in paths {
  212. let orig_path = path.unwrap().path();
  213. let ext = Path::new(&orig_path).extension();
  214. match ext {
  215. Some(_) => {
  216. if ext.unwrap() == "jpg" {
  217. num_paths += 1;
  218. println!("Is a image {}: {:?}", num_paths, orig_path);
  219. }
  220. }
  221. _ => {
  222. println!("Not an image: {:?}", orig_path);
  223. continue;
  224. }
  225. }
  226. // println!("Name: {}", path.unwrap().path().display())
  227. }
  228. // Currently 12 images in the test images directory
  229. assert_eq!(num_paths, 12);
  230. }
  231. /**
  232. * Updated test function. Assumes 3 images to a set and no hamming distances.
  233. * We don't need to confirm that the hamming distance calculation works in these tests.
  234. */
  235. fn test_imageset_hash(hash_type: hash::HashType,
  236. hash_precision: hash::Precision,
  237. max_hamming_distance: u64,
  238. image_paths: [&Path; 3],
  239. image_hashes: [u64; 3],
  240. lib: &PIHash) {
  241. let mut hashes: [u64; 3] = [0; 3];
  242. for index in 0..image_paths.len() {
  243. let image_path = image_paths[index];
  244. let calculated_hash = lib.get_perceptual_hash(&image_path, &hash_precision, &hash_type);
  245. println!("[{}] Image hashes for [{}] expected: [{}] actual: [{}]",
  246. hash_type,
  247. image_path.to_str().unwrap(),
  248. image_hashes[index],
  249. calculated_hash);
  250. assert_eq!(calculated_hash, image_hashes[index]);
  251. hashes[index] = calculated_hash;
  252. }
  253. for index in 0..hashes.len() {
  254. for index2 in 0..hashes.len() {
  255. if index == index2 {
  256. continue;
  257. } else {
  258. let distance = hash::calculate_hamming_distance(hashes[index], hashes[index2]);
  259. println!("Hashes [{}] and [{}] have a hamming distance of [{}] of a max allowed distance of [{}]",
  260. hashes[index],
  261. hashes[index2],
  262. distance,
  263. max_hamming_distance);
  264. assert!(distance <= max_hamming_distance);
  265. }
  266. }
  267. }
  268. }
  269. #[test]
  270. fn test_confirm_ahash_results() {
  271. // Prep_library
  272. let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR));
  273. // Sample_01 tests
  274. let sample_01_images: [&Path; 3] = [&Path::new("./test_images/sample_01_large.jpg"),
  275. &Path::new("./test_images/sample_01_medium.jpg"),
  276. &Path::new("./test_images/sample_01_small.jpg")];
  277. let sample_01_hashes: [u64; 3] = [7065306774709811078, 7065306774709811078, 7065306774172940166];
  278. test_imageset_hash(hash::HashType::AHash,
  279. hash::Precision::Medium,
  280. 1u64,
  281. sample_01_images,
  282. sample_01_hashes,
  283. &lib);
  284. // Sample_02 tests
  285. let sample_02_images: [&Path; 3] = [&Path::new("./test_images/sample_02_large.jpg"),
  286. &Path::new("./test_images/sample_02_medium.jpg"),
  287. &Path::new("./test_images/sample_02_small.jpg")];
  288. let sample_02_hashes: [u64; 3] = [18446744068986765312, 18446744069246812160, 18446744073541779456];
  289. test_imageset_hash(hash::HashType::AHash,
  290. hash::Precision::Medium,
  291. 3u64,
  292. sample_02_images,
  293. sample_02_hashes,
  294. &lib);
  295. // Sample_03 tests
  296. let sample_03_images: [&Path; 3] = [&Path::new("./test_images/sample_03_large.jpg"),
  297. &Path::new("./test_images/sample_03_medium.jpg"),
  298. &Path::new("./test_images/sample_03_small.jpg")];
  299. let sample_03_hashes: [u64; 3] = [108649334536274430, 126663733045756414, 108649334536274430];
  300. test_imageset_hash(hash::HashType::AHash,
  301. hash::Precision::Medium,
  302. 1u64,
  303. sample_03_images,
  304. sample_03_hashes,
  305. &lib);
  306. // Sample_04 tests
  307. let sample_04_images: [&Path; 3] = [&Path::new("./test_images/sample_04_large.jpg"),
  308. &Path::new("./test_images/sample_04_medium.jpg"),
  309. &Path::new("./test_images/sample_04_small.jpg")];
  310. let sample_04_hashes: [u64; 3] = [18446460933225054208, 18446460933225054208, 18446460933225054208];
  311. test_imageset_hash(hash::HashType::AHash,
  312. hash::Precision::Medium,
  313. 0u64,
  314. sample_04_images,
  315. sample_04_hashes,
  316. &lib);
  317. // Clean_Cache
  318. // super::teardown();
  319. }
  320. #[test]
  321. fn test_confirm_dhash_results() {
  322. // Prep_library
  323. let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR));
  324. // Sample_01 tests
  325. let sample_01_images: [&Path; 3] = [&Path::new("./test_images/sample_01_large.jpg"),
  326. &Path::new("./test_images/sample_01_medium.jpg"),
  327. &Path::new("./test_images/sample_01_small.jpg")];
  328. let sample_01_hashes: [u64; 3] = [18131474507607572478, 18131474507607572478, 18131474507607572478];
  329. test_imageset_hash(hash::HashType::DHash,
  330. hash::Precision::Medium,
  331. 0u64,
  332. sample_01_images,
  333. sample_01_hashes,
  334. &lib);
  335. // Sample_02 tests
  336. let sample_02_images: [&Path; 3] = [&Path::new("./test_images/sample_02_large.jpg"),
  337. &Path::new("./test_images/sample_02_medium.jpg"),
  338. &Path::new("./test_images/sample_02_small.jpg")];
  339. let sample_02_hashes: [u64; 3] = [10088065226894213121, 10088065226894213121, 10088065226894213121];
  340. test_imageset_hash(hash::HashType::DHash,
  341. hash::Precision::Medium,
  342. 0u64,
  343. sample_02_images,
  344. sample_02_hashes,
  345. &lib);
  346. // Sample_03 tests
  347. let sample_03_images: [&Path; 3] = [&Path::new("./test_images/sample_03_large.jpg"),
  348. &Path::new("./test_images/sample_03_medium.jpg"),
  349. &Path::new("./test_images/sample_03_small.jpg")];
  350. let sample_03_hashes: [u64; 3] = [144115181601817086, 144115181601817086, 144115181601817086];
  351. test_imageset_hash(hash::HashType::DHash,
  352. hash::Precision::Medium,
  353. 0u64,
  354. sample_03_images,
  355. sample_03_hashes,
  356. &lib);
  357. // Sample_04 tests
  358. let sample_04_images: [&Path; 3] = [&Path::new("./test_images/sample_04_large.jpg"),
  359. &Path::new("./test_images/sample_04_medium.jpg"),
  360. &Path::new("./test_images/sample_04_small.jpg")];
  361. let sample_04_hashes: [u64; 3] = [18374262326015557633, 18374262326015557633, 18374262326283993089];
  362. test_imageset_hash(hash::HashType::DHash,
  363. hash::Precision::Medium,
  364. 1u64,
  365. sample_04_images,
  366. sample_04_hashes,
  367. &lib);
  368. // Clean_Cache
  369. // super::teardown();
  370. }
  371. #[test]
  372. fn test_confirm_phash_results() {
  373. // Prep_library
  374. let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR));
  375. // Sample_01 tests
  376. let sample_01_images: [&Path; 3] = [&Path::new("./test_images/sample_01_large.jpg"),
  377. &Path::new("./test_images/sample_01_medium.jpg"),
  378. &Path::new("./test_images/sample_01_small.jpg")];
  379. let sample_01_hashes: [u64; 3] = [72410537899606272, 72410537899606272, 72410537899606400];
  380. test_imageset_hash(hash::HashType::PHash,
  381. hash::Precision::Medium,
  382. 1u64,
  383. sample_01_images,
  384. sample_01_hashes,
  385. &lib);
  386. // Sample_02 tests
  387. let sample_02_images: [&Path; 3] = [&Path::new("./test_images/sample_02_large.jpg"),
  388. &Path::new("./test_images/sample_02_medium.jpg"),
  389. &Path::new("./test_images/sample_02_small.jpg")];
  390. let sample_02_hashes: [u64; 3] = [5620563253458370560, 5620562703702556672, 5620562703702556672];
  391. test_imageset_hash(hash::HashType::PHash,
  392. hash::Precision::Medium,
  393. 1u64,
  394. sample_02_images,
  395. sample_02_hashes,
  396. &lib);
  397. // Sample_03 tests
  398. let sample_03_images: [&Path; 3] = [&Path::new("./test_images/sample_03_large.jpg"),
  399. &Path::new("./test_images/sample_03_medium.jpg"),
  400. &Path::new("./test_images/sample_03_small.jpg")];
  401. let sample_03_hashes: [u64; 3] = [6926536226895822848, 6926536226895822848, 6926536226895822848];
  402. test_imageset_hash(hash::HashType::PHash,
  403. hash::Precision::Medium,
  404. 0u64,
  405. sample_03_images,
  406. sample_03_hashes,
  407. &lib);
  408. // Sample_04 tests
  409. let sample_04_images: [&Path; 3] = [&Path::new("./test_images/sample_04_large.jpg"),
  410. &Path::new("./test_images/sample_04_medium.jpg"),
  411. &Path::new("./test_images/sample_04_small.jpg")];
  412. let sample_04_hashes: [u64; 3] = [11430286023502856200, 10997940459275288576, 11142055647351144448];
  413. test_imageset_hash(hash::HashType::PHash,
  414. hash::Precision::Medium,
  415. 3u64,
  416. sample_04_images,
  417. sample_04_hashes,
  418. &lib);
  419. // Clean_Cache
  420. // super::teardown();
  421. }
  422. #[cfg(feature = "bench")]
  423. #[bench]
  424. fn bench_with_cache(bench: &mut Bencher) -> () {
  425. // Prep_library
  426. let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR));
  427. // Setup the caches to make sure we're good to properly bench
  428. // All phashes so that the matricies are pulled from cache as well
  429. lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"),
  430. &hash::Precision::Medium,
  431. &hash::HashType::PHash);
  432. bench.iter(|| {
  433. // Sample_01 Bench
  434. lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"),
  435. &hash::Precision::Medium,
  436. &hash::HashType::PHash);
  437. })
  438. }
  439. #[cfg(feature = "bench")]
  440. #[bench]
  441. fn bench_without_cache(bench: &mut Bencher) -> () {
  442. // Prep_library
  443. let lib = PIHash::new(None);
  444. bench.iter(|| {
  445. // Sample_01 Bench
  446. lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"),
  447. &hash::Precision::Medium,
  448. &hash::HashType::PHash);
  449. })
  450. }
  451. }