|
@ -20,58 +20,66 @@ use cache::Cache; |
|
|
|
|
|
|
|
|
#[repr(C)]
|
|
|
#[repr(C)]
|
|
|
pub struct PIHash<'a> {
|
|
|
pub struct PIHash<'a> {
|
|
|
cache: Option<Cache<'a>>
|
|
|
|
|
|
|
|
|
cache: Option<Cache<'a>>,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
impl<'a> PIHash<'a> {
|
|
|
impl<'a> PIHash<'a> {
|
|
|
/**
|
|
|
|
|
|
* Create a new pihash library, and initialize a cache of a path is passed. If none is passed then no cache is initialized or used with the library
|
|
|
|
|
|
*/
|
|
|
|
|
|
pub fn new(cache_path: Option<&'a str>) -> PIHash<'a> {
|
|
|
|
|
|
match cache_path {
|
|
|
|
|
|
Some(path) => {
|
|
|
|
|
|
let cache = Cache { cache_dir: path, use_cache: true};
|
|
|
|
|
|
match cache.init() {
|
|
|
|
|
|
Ok(_) => PIHash { cache: Some(cache) },
|
|
|
|
|
|
Err(e) => {
|
|
|
|
|
|
println!("Error creating library with cache: {}", e);
|
|
|
|
|
|
PIHash { cache: None }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
None => PIHash { cache: None },
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn get_perceptual_hash(&self, path: &Path, precision: &hash::Precision, hash_type: &hash::HashType) -> u64 {
|
|
|
|
|
|
hash::get_perceptual_hash(&path, &precision, &hash_type, &self.cache)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn get_phashes(&self, path: &'a Path) -> hash::PerceptualHashes {
|
|
|
|
|
|
hash::get_perceptual_hashes(path, &hash::Precision::Medium, &self.cache)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn get_ahash(&self, path: &Path) -> u64 {
|
|
|
|
|
|
hash::get_perceptual_hash(&path,
|
|
|
|
|
|
&hash::Precision::Medium,
|
|
|
|
|
|
&hash::HashType::AHash,
|
|
|
|
|
|
&self.cache)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn get_dhash(&self, path: &Path) -> u64 {
|
|
|
|
|
|
hash::get_perceptual_hash(&path,
|
|
|
|
|
|
&hash::Precision::Medium,
|
|
|
|
|
|
&hash::HashType::DHash,
|
|
|
|
|
|
&self.cache)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn get_phash(&self, path: &Path) -> u64 {
|
|
|
|
|
|
hash::get_perceptual_hash(&path,
|
|
|
|
|
|
&hash::Precision::Medium,
|
|
|
|
|
|
&hash::HashType::PHash,
|
|
|
|
|
|
&self.cache)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Create a new pihash library, and initialize a cache of a path is passed.
|
|
|
|
|
|
* If none is passed then no cache is initialized or used with the library
|
|
|
|
|
|
*/
|
|
|
|
|
|
pub fn new(cache_path: Option<&'a str>) -> PIHash<'a> {
|
|
|
|
|
|
match cache_path {
|
|
|
|
|
|
Some(path) => {
|
|
|
|
|
|
let cache = Cache {
|
|
|
|
|
|
cache_dir: path,
|
|
|
|
|
|
use_cache: true,
|
|
|
|
|
|
};
|
|
|
|
|
|
match cache.init() {
|
|
|
|
|
|
Ok(_) => PIHash { cache: Some(cache) },
|
|
|
|
|
|
Err(e) => {
|
|
|
|
|
|
println!("Error creating library with cache: {}", e);
|
|
|
|
|
|
PIHash { cache: None }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
None => PIHash { cache: None },
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn get_perceptual_hash(&self,
|
|
|
|
|
|
path: &Path,
|
|
|
|
|
|
precision: &hash::Precision,
|
|
|
|
|
|
hash_type: &hash::HashType)
|
|
|
|
|
|
-> u64 {
|
|
|
|
|
|
hash::get_perceptual_hash(&path, &precision, &hash_type, &self.cache)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn get_phashes(&self, path: &'a Path) -> hash::PerceptualHashes {
|
|
|
|
|
|
hash::get_perceptual_hashes(path, &hash::Precision::Medium, &self.cache)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn get_ahash(&self, path: &Path) -> u64 {
|
|
|
|
|
|
hash::get_perceptual_hash(&path,
|
|
|
|
|
|
&hash::Precision::Medium,
|
|
|
|
|
|
&hash::HashType::AHash,
|
|
|
|
|
|
&self.cache)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn get_dhash(&self, path: &Path) -> u64 {
|
|
|
|
|
|
hash::get_perceptual_hash(&path,
|
|
|
|
|
|
&hash::Precision::Medium,
|
|
|
|
|
|
&hash::HashType::DHash,
|
|
|
|
|
|
&self.cache)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn get_phash(&self, path: &Path) -> u64 {
|
|
|
|
|
|
hash::get_perceptual_hash(&path,
|
|
|
|
|
|
&hash::Precision::Medium,
|
|
|
|
|
|
&hash::HashType::PHash,
|
|
|
|
|
|
&self.cache)
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
/**
|
|
@ -86,24 +94,24 @@ pub fn get_hamming_distance(hash1: u64, hash2: u64) -> u64 { |
|
|
|
|
|
|
|
|
#[no_mangle]
|
|
|
#[no_mangle]
|
|
|
pub extern "C" fn ext_init(cache_path_char: *const libc::c_char) -> *const libc::c_void {
|
|
|
pub extern "C" fn ext_init(cache_path_char: *const libc::c_char) -> *const libc::c_void {
|
|
|
unsafe {
|
|
|
|
|
|
|
|
|
unsafe {
|
|
|
let path_cstr = CStr::from_ptr(cache_path_char);
|
|
|
let path_cstr = CStr::from_ptr(cache_path_char);
|
|
|
let path_str = match path_cstr.to_str() {
|
|
|
let path_str = match path_cstr.to_str() {
|
|
|
Ok(path) => Some(path),
|
|
|
|
|
|
Err(_) => None,
|
|
|
|
|
|
|
|
|
Ok(path) => Some(path),
|
|
|
|
|
|
Err(_) => None,
|
|
|
};
|
|
|
};
|
|
|
//println!("Created new lib, with cache at {}", path_str.unwrap());
|
|
|
//println!("Created new lib, with cache at {}", path_str.unwrap());
|
|
|
let lib = Box::new(PIHash::new(path_str));
|
|
|
let lib = Box::new(PIHash::new(path_str));
|
|
|
let ptr = Box::into_raw(lib) as *mut libc::c_void;
|
|
|
let ptr = Box::into_raw(lib) as *mut libc::c_void;
|
|
|
ptr
|
|
|
ptr
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
#[no_mangle]
|
|
|
#[no_mangle]
|
|
|
pub extern "C" fn ext_free(raw_lib: *const libc::c_void) {
|
|
|
pub extern "C" fn ext_free(raw_lib: *const libc::c_void) {
|
|
|
unsafe {
|
|
|
|
|
|
drop(Box::from_raw(raw_lib as *mut PIHash));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
|
|
drop(Box::from_raw(raw_lib as *mut PIHash));
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
#[no_mangle]
|
|
|
#[no_mangle]
|
|
@ -172,7 +180,7 @@ fn to_hex_string(bytes: &[u8]) -> String { |
|
|
|
|
|
|
|
|
// Module for the tests
|
|
|
// Module for the tests
|
|
|
//
|
|
|
//
|
|
|
#[cfg(test)]
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
mod tests {
|
|
|
|
|
|
|
|
|
use std::fs;
|
|
|
use std::fs;
|
|
@ -194,11 +202,11 @@ mod tests { |
|
|
Some(_) => {
|
|
|
Some(_) => {
|
|
|
if ext.unwrap() == "jpg" {
|
|
|
if ext.unwrap() == "jpg" {
|
|
|
num_paths += 1;
|
|
|
num_paths += 1;
|
|
|
println!("Is a image {}: {:?}", num_paths, orig_path) ;
|
|
|
|
|
|
|
|
|
println!("Is a image {}: {:?}", num_paths, orig_path);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
_ => {
|
|
|
_ => {
|
|
|
println!("Not an image: {:?}", orig_path) ;
|
|
|
|
|
|
|
|
|
println!("Not an image: {:?}", orig_path);
|
|
|
continue;
|
|
|
continue;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@ -221,31 +229,29 @@ mod tests { |
|
|
let mut hashes: [u64; 3] = [0; 3];
|
|
|
let mut hashes: [u64; 3] = [0; 3];
|
|
|
for index in 0..image_paths.len() {
|
|
|
for index in 0..image_paths.len() {
|
|
|
let image_path = image_paths[index];
|
|
|
let image_path = image_paths[index];
|
|
|
let calculated_hash = lib.get_perceptual_hash(&image_path,
|
|
|
|
|
|
&hash_precision,
|
|
|
|
|
|
&hash_type);
|
|
|
|
|
|
|
|
|
let calculated_hash = lib.get_perceptual_hash(&image_path, &hash_precision, &hash_type);
|
|
|
println!("Image hashes for [{}] expected: [{}] actual: [{}]",
|
|
|
println!("Image hashes for [{}] expected: [{}] actual: [{}]",
|
|
|
image_path.to_str().unwrap(),
|
|
|
image_path.to_str().unwrap(),
|
|
|
image_hashes[index],
|
|
|
image_hashes[index],
|
|
|
calculated_hash);
|
|
|
calculated_hash);
|
|
|
assert_eq!(calculated_hash,image_hashes[index]);
|
|
|
|
|
|
|
|
|
assert_eq!(calculated_hash, image_hashes[index]);
|
|
|
hashes[index] = calculated_hash;
|
|
|
hashes[index] = calculated_hash;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
for index in 0..hashes.len() {
|
|
|
for index in 0..hashes.len() {
|
|
|
for index2 in 0..hashes.len() {
|
|
|
|
|
|
if index == index2 {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
let distance = hash::calculate_hamming_distance(hashes[index], hashes[index2]);
|
|
|
|
|
|
println!("Hashes [{}] and [{}] have a hamming distance of [{}] of a max allowed distance of [{}]",
|
|
|
|
|
|
hashes[index],
|
|
|
|
|
|
hashes[index2],
|
|
|
|
|
|
distance,
|
|
|
|
|
|
max_hamming_distance);
|
|
|
|
|
|
assert!(distance <= max_hamming_distance);
|
|
|
|
|
|
|
|
|
for index2 in 0..hashes.len() {
|
|
|
|
|
|
if index == index2 {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
let distance = hash::calculate_hamming_distance(hashes[index], hashes[index2]);
|
|
|
|
|
|
println!("Hashes [{}] and [{}] have a hamming distance of [{}] of a max allowed distance of [{}]",
|
|
|
|
|
|
hashes[index],
|
|
|
|
|
|
hashes[index2],
|
|
|
|
|
|
distance,
|
|
|
|
|
|
max_hamming_distance);
|
|
|
|
|
|
assert!(distance <= max_hamming_distance);
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
@ -258,9 +264,7 @@ mod tests { |
|
|
let sample_01_images: [&Path; 3] = [&Path::new("./test_images/sample_01_large.jpg"),
|
|
|
let sample_01_images: [&Path; 3] = [&Path::new("./test_images/sample_01_large.jpg"),
|
|
|
&Path::new("./test_images/sample_01_medium.jpg"),
|
|
|
&Path::new("./test_images/sample_01_medium.jpg"),
|
|
|
&Path::new("./test_images/sample_01_small.jpg")];
|
|
|
&Path::new("./test_images/sample_01_small.jpg")];
|
|
|
let sample_01_hashes: [u64; 3] = [857051991849750,
|
|
|
|
|
|
857051991849750,
|
|
|
|
|
|
857051992374038];
|
|
|
|
|
|
|
|
|
let sample_01_hashes: [u64; 3] = [857051991849750, 857051991849750, 857051992374038];
|
|
|
test_imageset_hash(hash::HashType::AHash,
|
|
|
test_imageset_hash(hash::HashType::AHash,
|
|
|
hash::Precision::Medium,
|
|
|
hash::Precision::Medium,
|
|
|
1u64,
|
|
|
1u64,
|
|
@ -286,9 +290,8 @@ mod tests { |
|
|
let sample_03_images: [&Path; 3] = [&Path::new("./test_images/sample_03_large.jpg"),
|
|
|
let sample_03_images: [&Path; 3] = [&Path::new("./test_images/sample_03_large.jpg"),
|
|
|
&Path::new("./test_images/sample_03_medium.jpg"),
|
|
|
&Path::new("./test_images/sample_03_medium.jpg"),
|
|
|
&Path::new("./test_images/sample_03_small.jpg")];
|
|
|
&Path::new("./test_images/sample_03_small.jpg")];
|
|
|
let sample_03_hashes: [u64; 3] = [135670932300497406,
|
|
|
|
|
|
135670932300497406,
|
|
|
|
|
|
135670932300497406];
|
|
|
|
|
|
|
|
|
let sample_03_hashes: [u64; 3] =
|
|
|
|
|
|
[135670932300497406, 135670932300497406, 135670932300497406];
|
|
|
test_imageset_hash(hash::HashType::AHash,
|
|
|
test_imageset_hash(hash::HashType::AHash,
|
|
|
hash::Precision::Medium,
|
|
|
hash::Precision::Medium,
|
|
|
1u64,
|
|
|
1u64,
|
|
@ -351,9 +354,8 @@ mod tests { |
|
|
let sample_03_images: [&Path; 3] = [&Path::new("./test_images/sample_03_large.jpg"),
|
|
|
let sample_03_images: [&Path; 3] = [&Path::new("./test_images/sample_03_large.jpg"),
|
|
|
&Path::new("./test_images/sample_03_medium.jpg"),
|
|
|
&Path::new("./test_images/sample_03_medium.jpg"),
|
|
|
&Path::new("./test_images/sample_03_small.jpg")];
|
|
|
&Path::new("./test_images/sample_03_small.jpg")];
|
|
|
let sample_03_hashes: [u64; 3] = [262683193365159876,
|
|
|
|
|
|
225528496439353284,
|
|
|
|
|
|
225528496435158982];
|
|
|
|
|
|
|
|
|
let sample_03_hashes: [u64; 3] =
|
|
|
|
|
|
[262683193365159876, 225528496439353284, 225528496435158982];
|
|
|
test_imageset_hash(hash::HashType::DHash,
|
|
|
test_imageset_hash(hash::HashType::DHash,
|
|
|
hash::Precision::Medium,
|
|
|
hash::Precision::Medium,
|
|
|
4u64,
|
|
|
4u64,
|
|
@ -388,9 +390,7 @@ mod tests { |
|
|
let sample_01_images: [&Path; 3] = [&Path::new("./test_images/sample_01_large.jpg"),
|
|
|
let sample_01_images: [&Path; 3] = [&Path::new("./test_images/sample_01_large.jpg"),
|
|
|
&Path::new("./test_images/sample_01_medium.jpg"),
|
|
|
&Path::new("./test_images/sample_01_medium.jpg"),
|
|
|
&Path::new("./test_images/sample_01_small.jpg")];
|
|
|
&Path::new("./test_images/sample_01_small.jpg")];
|
|
|
let sample_01_hashes: [u64; 3] = [72357778504597504,
|
|
|
|
|
|
72357778504597504,
|
|
|
|
|
|
72357778504597504];
|
|
|
|
|
|
|
|
|
let sample_01_hashes: [u64; 3] = [72357778504597504, 72357778504597504, 72357778504597504];
|
|
|
test_imageset_hash(hash::HashType::PHash,
|
|
|
test_imageset_hash(hash::HashType::PHash,
|
|
|
hash::Precision::Medium,
|
|
|
hash::Precision::Medium,
|
|
|
0u64,
|
|
|
0u64,
|
|
@ -447,28 +447,34 @@ mod tests { |
|
|
#[cfg(feature = "bench")]
|
|
|
#[cfg(feature = "bench")]
|
|
|
#[bench]
|
|
|
#[bench]
|
|
|
fn bench_with_cache(bench: &mut Bencher) -> () {
|
|
|
fn bench_with_cache(bench: &mut Bencher) -> () {
|
|
|
// Prep_library
|
|
|
|
|
|
|
|
|
// Prep_library
|
|
|
let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR));
|
|
|
let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR));
|
|
|
|
|
|
|
|
|
// Setup the caches to make sure we're good to properly bench
|
|
|
// Setup the caches to make sure we're good to properly bench
|
|
|
// All phashes so that the matricies are pulled from cache as well
|
|
|
// All phashes so that the matricies are pulled from cache as well
|
|
|
lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"),&hash::Precision::Medium, &hash::HashType::PHash);
|
|
|
|
|
|
|
|
|
lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"),
|
|
|
|
|
|
&hash::Precision::Medium,
|
|
|
|
|
|
&hash::HashType::PHash);
|
|
|
|
|
|
|
|
|
bench.iter(|| {
|
|
|
bench.iter(|| {
|
|
|
// Sample_01 Bench
|
|
|
|
|
|
lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"), &hash::Precision::Medium, &hash::HashType::PHash);
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
// Sample_01 Bench
|
|
|
|
|
|
lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"),
|
|
|
|
|
|
&hash::Precision::Medium,
|
|
|
|
|
|
&hash::HashType::PHash);
|
|
|
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
#[cfg(feature = "bench")]
|
|
|
#[cfg(feature = "bench")]
|
|
|
#[bench]
|
|
|
#[bench]
|
|
|
fn bench_without_cache(bench: &mut Bencher) -> () {
|
|
|
fn bench_without_cache(bench: &mut Bencher) -> () {
|
|
|
// Prep_library
|
|
|
|
|
|
|
|
|
// Prep_library
|
|
|
let lib = PIHash::new(None);
|
|
|
let lib = PIHash::new(None);
|
|
|
|
|
|
|
|
|
bench.iter(|| {
|
|
|
bench.iter(|| {
|
|
|
// Sample_01 Bench
|
|
|
|
|
|
lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"), &hash::Precision::Medium, &hash::HashType::PHash);
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
// Sample_01 Bench
|
|
|
|
|
|
lib.get_perceptual_hash(&Path::new("./test_images/sample_01_large.jpg"),
|
|
|
|
|
|
&hash::Precision::Medium,
|
|
|
|
|
|
&hash::HashType::PHash);
|
|
|
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|