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.

708 lines
21 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
  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. extern crate serde;
  10. #[cfg(feature = "bench")]
  11. extern crate test;
  12. use std::ffi::CStr;
  13. use std::path::Path;
  14. use cache::Cache;
  15. pub mod cache;
  16. pub mod hash;
  17. #[repr(C)]
  18. pub struct PIHash {
  19. cache: Option<Cache>,
  20. }
  21. impl PIHash {
  22. /**
  23. * Create a new pihash library, and initialize a cache of a path is passed.
  24. * If none is passed then no cache is initialized or used with the library
  25. */
  26. pub fn new(cache_path: Option<&str>) -> PIHash {
  27. match cache_path {
  28. Some(path) => {
  29. let cache = Cache {
  30. cache_dir: String::from(path),
  31. use_cache: true,
  32. };
  33. match cache.init() {
  34. Ok(_) => PIHash { cache: Some(cache) },
  35. Err(e) => {
  36. println!("Error creating library with cache: {}", e);
  37. PIHash { cache: None }
  38. }
  39. }
  40. }
  41. None => PIHash { cache: None },
  42. }
  43. }
  44. pub fn get_perceptual_hash(
  45. &self,
  46. path: &Path,
  47. precision: &hash::Precision,
  48. hash_type: &hash::HashType,
  49. ) -> u64 {
  50. hash::get_perceptual_hash(&path, &precision, &hash_type, &self.cache)
  51. }
  52. pub fn get_pihashes(&self, path: &Path) -> hash::PerceptualHashes {
  53. hash::get_perceptual_hashes(&path, &hash::Precision::Medium, &self.cache)
  54. }
  55. pub fn get_ahash(&self, path: &Path) -> u64 {
  56. hash::get_perceptual_hash(
  57. &path,
  58. &hash::Precision::Medium,
  59. &hash::HashType::AHash,
  60. &self.cache,
  61. )
  62. }
  63. pub fn get_dhash(&self, path: &Path) -> u64 {
  64. hash::get_perceptual_hash(
  65. &path,
  66. &hash::Precision::Medium,
  67. &hash::HashType::DHash,
  68. &self.cache,
  69. )
  70. }
  71. pub fn get_phash(&self, path: &Path) -> u64 {
  72. hash::get_perceptual_hash(
  73. &path,
  74. &hash::Precision::Medium,
  75. &hash::HashType::PHash,
  76. &self.cache,
  77. )
  78. }
  79. }
  80. /**
  81. * Get the Hamming Distance between two hashes.
  82. * Represents the absolute difference between two numbers.
  83. */
  84. pub fn get_hamming_distance(hash1: u64, hash2: u64) -> u64 {
  85. hash::calculate_hamming_distance(hash1, hash2)
  86. }
  87. // External proxies for the get_*hash methods //
  88. #[no_mangle]
  89. pub extern "C" fn ext_init(cache_path_char: *const libc::c_char) -> *const libc::c_void {
  90. unsafe {
  91. let path_cstr = CStr::from_ptr(cache_path_char);
  92. let path_str = match path_cstr.to_str() {
  93. Ok(path) => Some(path),
  94. Err(_) => None,
  95. };
  96. // println!("Created new lib, with cache at {}", path_str.unwrap());
  97. let lib = Box::new(PIHash::new(path_str));
  98. let ptr = Box::into_raw(lib) as *mut libc::c_void;
  99. ptr
  100. }
  101. }
  102. #[no_mangle]
  103. pub extern "C" fn ext_free(raw_lib: *const libc::c_void) {
  104. unsafe {
  105. drop(Box::from_raw(raw_lib as *mut PIHash));
  106. }
  107. }
  108. #[no_mangle]
  109. pub extern "C" fn ext_get_ahash(lib: &PIHash, path_char: *const libc::c_char) -> u64 {
  110. unsafe {
  111. let path_str = CStr::from_ptr(path_char);
  112. let image_path = get_str_from_cstr(path_str);
  113. let path = Path::new(&image_path);
  114. lib.get_ahash(path)
  115. }
  116. }
  117. #[no_mangle]
  118. pub extern "C" fn ext_get_dhash(lib: &PIHash, path_char: *const libc::c_char) -> u64 {
  119. unsafe {
  120. let path_str = CStr::from_ptr(path_char);
  121. let image_path = get_str_from_cstr(path_str);
  122. let path = Path::new(&image_path);
  123. lib.get_dhash(path)
  124. }
  125. }
  126. #[no_mangle]
  127. pub extern "C" fn ext_get_phash(lib: &PIHash, path_char: *const libc::c_char) -> u64 {
  128. unsafe {
  129. let path_str = CStr::from_ptr(path_char);
  130. let image_path = get_str_from_cstr(path_str);
  131. let path = Path::new(&image_path);
  132. lib.get_phash(path)
  133. }
  134. }
  135. #[repr(C)]
  136. pub struct PIHashes {
  137. ahash: u64,
  138. dhash: u64,
  139. phash: u64,
  140. }
  141. #[no_mangle]
  142. pub extern "C" fn ext_get_pihashes(lib: &PIHash, path_char: *const libc::c_char) -> *mut PIHashes {
  143. unsafe {
  144. let path_str = CStr::from_ptr(path_char);
  145. let image_path = get_str_from_cstr(path_str);
  146. let path = Path::new(&image_path);
  147. let pihashes = lib.get_pihashes(path);
  148. Box::into_raw(Box::new(PIHashes {
  149. ahash: pihashes.ahash,
  150. dhash: pihashes.dhash,
  151. phash: pihashes.phash,
  152. }))
  153. }
  154. }
  155. #[no_mangle]
  156. pub extern "C" fn ext_free_pihashes(raw_pihashes: *const libc::c_void) {
  157. unsafe {
  158. drop(Box::from_raw(raw_pihashes as *mut PIHashes));
  159. }
  160. }
  161. fn get_str_from_cstr(path_str: &CStr) -> &str {
  162. match path_str.to_str() {
  163. Ok(result) => result,
  164. Err(e) => {
  165. println!(
  166. "Error: {}. Unable to parse '{}'",
  167. e,
  168. to_hex_string(path_str.to_bytes())
  169. );
  170. panic!("Unable to parse path")
  171. }
  172. }
  173. }
  174. fn to_hex_string(bytes: &[u8]) -> String {
  175. println!("length: {}", bytes.len());
  176. let mut strs: Vec<String> = Vec::new();
  177. for byte in bytes {
  178. // println!("{:02x}", byte);
  179. strs.push(format!("{:02x}", byte));
  180. }
  181. strs.join("\\x")
  182. }
  183. // Module for the tests
  184. //
  185. #[cfg(test)]
  186. mod tests {
  187. use std::fs;
  188. use std::path::Path;
  189. use cache;
  190. use hash;
  191. use hash::{PerceptualHash, PerceptualHashes};
  192. use super::PIHash;
  193. #[cfg(feature = "bench")]
  194. use super::test::Bencher;
  195. thread_local!(static LIB: PIHash = PIHash::new(Some(cache::DEFAULT_CACHE_DIR)));
  196. thread_local!(static NO_CACHE_LIB: PIHash = PIHash::new(None));
  197. #[test]
  198. fn test_can_get_test_images() {
  199. let paths = fs::read_dir(&Path::new("./test_images")).unwrap();
  200. let mut num_paths = 0;
  201. for path in paths {
  202. let orig_path = path.unwrap().path();
  203. let ext = Path::new(&orig_path).extension();
  204. match ext {
  205. Some(_) => {
  206. if ext.unwrap() == "jpg" {
  207. num_paths += 1;
  208. println!("Is a image {}: {:?}", num_paths, orig_path);
  209. }
  210. }
  211. _ => {
  212. println!("Not an image: {:?}", orig_path);
  213. continue;
  214. }
  215. }
  216. // println!("Name: {}", path.unwrap().path().display())
  217. }
  218. // Currently 12 images in the test images directory
  219. assert_eq!(num_paths, 12);
  220. }
  221. /**
  222. * Updated test function. Assumes 3 images to a set and no hamming distances.
  223. * We don't need to confirm that the hamming distance calculation works in these tests.
  224. */
  225. fn test_image_set_hash(
  226. hash_type: hash::HashType,
  227. hash_precision: hash::Precision,
  228. max_hamming_distance: u64,
  229. image_paths: [&Path; 3],
  230. image_hashes: [u64; 3],
  231. lib: &PIHash,
  232. ) {
  233. let mut hashes: [u64; 3] = [0; 3];
  234. for index in 0..image_paths.len() {
  235. // println!("{}, {:?}", index, image_paths[index]);
  236. let image_path = image_paths[index];
  237. let calculated_hash = lib.get_perceptual_hash(&image_path, &hash_precision, &hash_type);
  238. println!(
  239. "[{}] Image hashes for [{}] expected: [{}] actual: [{}]",
  240. hash_type,
  241. image_path.to_str().unwrap(),
  242. image_hashes[index],
  243. calculated_hash
  244. );
  245. hashes[index] = calculated_hash;
  246. }
  247. assert_eq!(hashes, image_hashes);
  248. for index in 0..hashes.len() {
  249. for index2 in 0..hashes.len() {
  250. if index == index2 {
  251. continue;
  252. } else {
  253. let distance = hash::calculate_hamming_distance(hashes[index], hashes[index2]);
  254. println!("Hashes [{}] and [{}] have a hamming distance of [{}] of a max allowed distance of [{}]",
  255. hashes[index],
  256. hashes[index2],
  257. distance,
  258. max_hamming_distance);
  259. assert!(distance <= max_hamming_distance);
  260. }
  261. }
  262. }
  263. }
  264. /**
  265. * Test image set with and without caching
  266. */
  267. fn test_image_set(
  268. hash_type: hash::HashType,
  269. hash_precision: hash::Precision,
  270. max_hamming_distance: u64,
  271. image_paths: [&Path; 3],
  272. image_hashes: [u64; 3],
  273. ) {
  274. LIB.with(|lib| {
  275. test_image_set_hash(
  276. hash_type,
  277. hash_precision,
  278. max_hamming_distance,
  279. image_paths,
  280. image_hashes,
  281. lib,
  282. );
  283. });
  284. NO_CACHE_LIB.with(|lib| {
  285. test_image_set_hash(
  286. hash_type,
  287. hash_precision,
  288. max_hamming_distance,
  289. image_paths,
  290. image_hashes,
  291. lib,
  292. );
  293. });
  294. }
  295. /**
  296. * Updated test function. Assumes 3 images to a set and no hamming distances.
  297. * We don't need to confirm that the hamming distance calculation works in these tests.
  298. */
  299. fn test_images_hashes(
  300. image_hashes: &[PerceptualHashes],
  301. lib: &PIHash,
  302. ) {
  303. let mut hashes = vec![];
  304. for index in 0..image_hashes.len() {
  305. // println!("{}, {:?}", index, image_paths[index]);
  306. let image_path = Path::new(&image_hashes[index].orig_path);
  307. let calculated_hash = lib.get_pihashes(&image_path);
  308. println!(
  309. "Image hashes expected: [{:?}] actual: [{:?}]",
  310. image_hashes[index],
  311. calculated_hash
  312. );
  313. hashes.push(calculated_hash);
  314. }
  315. for index in 0..image_hashes.len() {
  316. assert_eq!(hashes[index], image_hashes[index]);
  317. }
  318. //
  319. // for index in 0..hashes.len() {
  320. // for index2 in 0..hashes.len() {
  321. // if index == index2 {
  322. // continue;
  323. // } else {
  324. // let distance = hash::calculate_hamming_distance(hashes[index], hashes[index2]);
  325. // println!("Hashes [{}] and [{}] have a hamming distance of [{}] of a max allowed distance of [{}]",
  326. // hashes[index],
  327. // hashes[index2],
  328. // distance,
  329. // max_hamming_distance);
  330. // assert!(distance <= max_hamming_distance);
  331. // }
  332. // }
  333. // }
  334. }
  335. /**
  336. * Test images with and without caching
  337. */
  338. fn test_images(image_hashes: &[PerceptualHashes]) {
  339. LIB.with(|lib| {
  340. test_images_hashes(
  341. &image_hashes,
  342. lib,
  343. );
  344. });
  345. NO_CACHE_LIB.with(|lib| {
  346. test_images_hashes(
  347. &image_hashes,
  348. lib,
  349. );
  350. });
  351. }
  352. #[test]
  353. fn test_confirm_ahash_results_sample_01() {
  354. let sample_01_images: [&Path; 3] = [
  355. &Path::new("./test_images/sample_01_large.jpg"),
  356. &Path::new("./test_images/sample_01_medium.jpg"),
  357. &Path::new("./test_images/sample_01_small.jpg"),
  358. ];
  359. let sample_01_hashes: [u64; 3] = [857051991849750, 857051991849750, 857051991849750];
  360. test_image_set(
  361. hash::HashType::AHash,
  362. hash::Precision::Medium,
  363. 0u64,
  364. sample_01_images,
  365. sample_01_hashes,
  366. );
  367. }
  368. #[test]
  369. fn test_confirm_ahash_results_sample_02() {
  370. let sample_02_images: [&Path; 3] = [
  371. &Path::new("./test_images/sample_02_large.jpg"),
  372. &Path::new("./test_images/sample_02_medium.jpg"),
  373. &Path::new("./test_images/sample_02_small.jpg"),
  374. ];
  375. let sample_02_hashes: [u64; 3] = [
  376. 18446744073441116160,
  377. 18446744073441116160,
  378. 18446744073441116160,
  379. ];
  380. test_image_set(
  381. hash::HashType::AHash,
  382. hash::Precision::Medium,
  383. 0u64,
  384. sample_02_images,
  385. sample_02_hashes,
  386. );
  387. }
  388. #[test]
  389. fn test_confirm_ahash_results_sample_03() {
  390. let sample_03_images: [&Path; 3] = [
  391. &Path::new("./test_images/sample_03_large.jpg"),
  392. &Path::new("./test_images/sample_03_medium.jpg"),
  393. &Path::new("./test_images/sample_03_small.jpg"),
  394. ];
  395. let sample_03_hashes: [u64; 3] =
  396. [135670932300497406, 135670932300497406, 135670932300497406];
  397. test_image_set(
  398. hash::HashType::AHash,
  399. hash::Precision::Medium,
  400. 0u64,
  401. sample_03_images,
  402. sample_03_hashes,
  403. );
  404. }
  405. #[test]
  406. fn test_confirm_ahash_results_sample_04() {
  407. let sample_04_images: [&Path; 3] = [
  408. &Path::new("./test_images/sample_04_large.jpg"),
  409. &Path::new("./test_images/sample_04_medium.jpg"),
  410. &Path::new("./test_images/sample_04_small.jpg"),
  411. ];
  412. let sample_04_hashes: [u64; 3] = [
  413. 18446460933225054208,
  414. 18446460933225054208,
  415. 18446460933225054208,
  416. ];
  417. LIB.with(|lib| {
  418. test_image_set_hash(
  419. hash::HashType::AHash,
  420. hash::Precision::Medium,
  421. 0u64,
  422. sample_04_images,
  423. sample_04_hashes,
  424. lib,
  425. );
  426. });
  427. }
  428. #[test]
  429. fn test_confirm_dhash_results_sample_01() {
  430. let sample_01_images: [&Path; 3] = [
  431. &Path::new("./test_images/sample_01_large.jpg"),
  432. &Path::new("./test_images/sample_01_medium.jpg"),
  433. &Path::new("./test_images/sample_01_small.jpg"),
  434. ];
  435. let sample_01_hashes: [u64; 3] = [
  436. 3404580580803739582,
  437. 3404580580803739582,
  438. 3404580580803739582,
  439. ];
  440. LIB.with(|lib| {
  441. test_image_set_hash(
  442. hash::HashType::DHash,
  443. hash::Precision::Medium,
  444. 0u64,
  445. sample_01_images,
  446. sample_01_hashes,
  447. lib,
  448. );
  449. });
  450. }
  451. #[test]
  452. fn test_confirm_dhash_results_sample_02() {
  453. let sample_02_images: [&Path; 3] = [
  454. &Path::new("./test_images/sample_02_large.jpg"),
  455. &Path::new("./test_images/sample_02_medium.jpg"),
  456. &Path::new("./test_images/sample_02_small.jpg"),
  457. ];
  458. let sample_02_hashes: [u64; 3] = [
  459. 14726771606135242753,
  460. 14726771606135242753,
  461. 14726771606135242753,
  462. ];
  463. LIB.with(|lib| {
  464. test_image_set_hash(
  465. hash::HashType::DHash,
  466. hash::Precision::Medium,
  467. 0u64,
  468. sample_02_images,
  469. sample_02_hashes,
  470. lib,
  471. );
  472. });
  473. }
  474. #[test]
  475. fn test_confirm_dhash_results_sample_03() {
  476. let sample_03_images: [&Path; 3] = [
  477. &Path::new("./test_images/sample_03_large.jpg"),
  478. &Path::new("./test_images/sample_03_medium.jpg"),
  479. &Path::new("./test_images/sample_03_small.jpg"),
  480. ];
  481. let sample_03_hashes: [u64; 3] =
  482. [144115181601817086, 144115181601817086, 144115181601817086];
  483. LIB.with(|lib| {
  484. test_image_set_hash(
  485. hash::HashType::DHash,
  486. hash::Precision::Medium,
  487. 0u64,
  488. sample_03_images,
  489. sample_03_hashes,
  490. lib,
  491. );
  492. });
  493. }
  494. #[test]
  495. fn test_confirm_dhash_results_sample_04() {
  496. let sample_04_images: [&Path; 3] = [
  497. &Path::new("./test_images/sample_04_large.jpg"),
  498. &Path::new("./test_images/sample_04_medium.jpg"),
  499. &Path::new("./test_images/sample_04_small.jpg"),
  500. ];
  501. let sample_04_hashes: [u64; 3] = [
  502. 18374262188442386433,
  503. 18374262188442386433,
  504. 18374262188442386433,
  505. ];
  506. LIB.with(|lib| {
  507. test_image_set_hash(
  508. hash::HashType::DHash,
  509. hash::Precision::Medium,
  510. 0u64,
  511. sample_04_images,
  512. sample_04_hashes,
  513. lib,
  514. );
  515. });
  516. }
  517. #[test]
  518. fn test_confirm_phash_results_sample_01() {
  519. let sample_01_images: [&Path; 3] = [
  520. &Path::new("./test_images/sample_01_large.jpg"),
  521. &Path::new("./test_images/sample_01_medium.jpg"),
  522. &Path::new("./test_images/sample_01_small.jpg"),
  523. ];
  524. let sample_01_hashes: [u64; 3] = [72357778504597504, 72357778504597504, 72357778504597504];
  525. test_image_set(
  526. hash::HashType::PHash,
  527. hash::Precision::Medium,
  528. 0u64,
  529. sample_01_images,
  530. sample_01_hashes,
  531. );
  532. }
  533. #[test]
  534. fn test_confirm_phash_results_sample_02() {
  535. let sample_02_images: [&Path; 3] = [
  536. &Path::new("./test_images/sample_02_large.jpg"),
  537. &Path::new("./test_images/sample_02_medium.jpg"),
  538. &Path::new("./test_images/sample_02_small.jpg"),
  539. ];
  540. let sample_02_hashes: [u64; 3] = [
  541. 5332332327550844928,
  542. 5332332327550844928,
  543. 5332332327550844928,
  544. ];
  545. test_image_set(
  546. hash::HashType::PHash,
  547. hash::Precision::Medium,
  548. 0u64,
  549. sample_02_images,
  550. sample_02_hashes,
  551. );
  552. }
  553. #[test]
  554. fn test_confirm_phash_results_sample_03() {
  555. let sample_03_images: [&Path; 3] = [
  556. &Path::new("./test_images/sample_03_large.jpg"),
  557. &Path::new("./test_images/sample_03_medium.jpg"),
  558. &Path::new("./test_images/sample_03_small.jpg"),
  559. ];
  560. let sample_03_hashes: [u64; 3] = [
  561. 6917529027641081856,
  562. 6917529027641081856,
  563. 6917529027641081856,
  564. ];
  565. test_image_set(
  566. hash::HashType::PHash,
  567. hash::Precision::Medium,
  568. 0u64,
  569. sample_03_images,
  570. sample_03_hashes,
  571. );
  572. }
  573. #[test]
  574. fn test_confirm_phash_results_sample_04() {
  575. let sample_04_images: [&Path; 3] = [
  576. &Path::new("./test_images/sample_04_large.jpg"),
  577. &Path::new("./test_images/sample_04_medium.jpg"),
  578. &Path::new("./test_images/sample_04_small.jpg"),
  579. ];
  580. let sample_04_hashes: [u64; 3] = [
  581. 10997931646002397184,
  582. 10997931646002397184,
  583. 10997931646002397184,
  584. ];
  585. test_image_set(
  586. hash::HashType::PHash,
  587. hash::Precision::Medium,
  588. 0u64,
  589. sample_04_images,
  590. sample_04_hashes,
  591. );
  592. }
  593. #[test]
  594. fn test_confirm_pihash_results() {
  595. let sample_hashes: [PerceptualHashes; 4] = [
  596. PerceptualHashes {
  597. orig_path: "./test_images/sample_01_large.jpg".to_string(),
  598. ahash: 857051991849750,
  599. dhash: 3404580580803739582,
  600. phash: 72357778504597504,
  601. },
  602. PerceptualHashes {
  603. orig_path: "./test_images/sample_02_large.jpg".to_string(),
  604. ahash: 18446744073441116160,
  605. dhash: 14726771606135242753,
  606. phash: 5332332327550844928,
  607. },
  608. PerceptualHashes {
  609. orig_path: "./test_images/sample_03_large.jpg".to_string(),
  610. ahash: 135670932300497406,
  611. dhash: 144115181601817086,
  612. phash: 6917529027641081856,
  613. },
  614. PerceptualHashes {
  615. orig_path: "./test_images/sample_04_large.jpg".to_string(),
  616. ahash: 18446460933225054208,
  617. dhash: 18374262188442386433,
  618. phash: 10997931646002397184,
  619. }
  620. ];
  621. test_images(&sample_hashes);
  622. }
  623. #[cfg(feature = "bench")]
  624. #[bench]
  625. fn bench_with_cache(bench: &mut Bencher) -> () {
  626. // Prep_library
  627. let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR));
  628. // Setup the caches to make sure we're good to properly bench
  629. // All pihashes so that the matrices are pulled from cache as well
  630. lib.get_perceptual_hash(
  631. &Path::new("./test_images/sample_01_large.jpg"),
  632. &hash::Precision::Medium,
  633. &hash::HashType::PHash,
  634. );
  635. bench.iter(|| {
  636. lib.get_perceptual_hash(
  637. &Path::new("./test_images/sample_01_large.jpg"),
  638. &hash::Precision::Medium,
  639. &hash::HashType::PHash,
  640. );
  641. })
  642. }
  643. #[cfg(feature = "bench")]
  644. #[bench]
  645. fn bench_without_cache(bench: &mut Bencher) -> () {
  646. // Prep_library
  647. let lib = PIHash::new(None);
  648. bench.iter(|| {
  649. lib.get_perceptual_hash(
  650. &Path::new("./test_images/sample_01_large.jpg"),
  651. &hash::Precision::Medium,
  652. &hash::HashType::PHash,
  653. );
  654. })
  655. }
  656. }