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.

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