Browse Source

Manage merge conflicts

master
Drew Short 5 years ago
parent
commit
c08bf42ca4
  1. 14
      FFI-tests/ffi_test.py
  2. 84
      src/cache.rs
  3. 16
      src/hash/ahash.rs
  4. 16
      src/hash/dhash.rs
  5. 107
      src/hash/mod.rs
  6. 109
      src/hash/phash.rs
  7. 434
      src/lib.rs
  8. 31
      src/main.rs

14
FFI-tests/ffi_test.py

@ -2,7 +2,6 @@
from ctypes import *
from sys import exit, platform
import os
large_image1_path = "../test_images/sample_01_large.jpg".encode(encoding="utf-8")
medium_image1_path = "../test_images/sample_01_medium.jpg".encode(encoding="utf-8")
@ -57,6 +56,7 @@ class PIHashes(Structure):
# Setting the ctypes return type references for the foreign functions
# returns a pointer to the library that we'll need to pass to all function calls
lib.ext_init.restype = c_void_p
lib.ext_init.argtypes = [c_char_p]
# Returns a longlong hash, takes a pointer and a string
lib.ext_get_ahash.restype = c_ulonglong
lib.ext_get_ahash.argtypes = [c_void_p, c_char_p]
@ -64,14 +64,14 @@ lib.ext_get_dhash.restype = c_ulonglong
lib.ext_get_dhash.argtypes = [c_void_p, c_char_p]
lib.ext_get_phash.restype = c_ulonglong
lib.ext_get_phash.argtypes = [c_void_p, c_char_p]
lib.ext_get_phashes.restype = c_void_p
lib.ext_get_phashes.argtypes = [c_void_p, c_char_p]
lib.ext_free_phashes.argtypes = [c_void_p]
lib.ext_get_pihashes.restype = c_void_p
lib.ext_get_pihashes.argtypes = [c_void_p, c_char_p]
lib.ext_free_pihashes.argtypes = [c_void_p]
# Takes a pointer and frees the struct at that memory location
lib.ext_free.argtypes = [c_void_p]
#initialize the library
lib_struct = lib.ext_init("./.hash_cache".encode(encoding="utf-8"))
lib_struct = lib.ext_init("./.hash_cache".encode('utf-8'))
#print("Pointer to lib_struct: ", lib_struct)
@ -81,9 +81,9 @@ lib_struct = lib.ext_init("./.hash_cache".encode(encoding="utf-8"))
for image in test_images:
print("Requesting hashes for: %s"% image)
phashes = lib.ext_get_phashes(lib_struct, image)
phashes = lib.ext_get_pihashes(lib_struct, image)
pihashes = PIHashes.from_address(phashes)
lib.ext_free_phashes(phashes)
lib.ext_free_pihashes(phashes)
print("ahash: %i"% unsigned64(pihashes.ahash))
print("dhash: %i"% unsigned64(pihashes.dhash))
print("phash: %i"% unsigned64(pihashes.phash))

84
src/cache.rs

@ -9,7 +9,7 @@ extern crate num;
extern crate sha1;
use std::default::Default;
use std::fs::{create_dir_all, remove_dir_all, File};
use std::fs::{create_dir_all, File, remove_dir_all};
use std::io::{Error, ErrorKind, Read, Write};
use std::option::Option;
use std::path::Path;
@ -18,9 +18,9 @@ use std::str::FromStr;
use super::rustc_serialize::json;
use self::flate2::Compression;
use self::flate2::read::ZlibDecoder;
use self::flate2::write::ZlibEncoder;
use self::flate2::Compression;
use self::image::DynamicImage;
use self::sha1::Sha1;
@ -58,26 +58,26 @@ impl PartialEq<CacheMetadata> for CacheMetadata {
* Structure to hold implementation of the cache
*/
#[repr(C)]
pub struct Cache<'a> {
pub cache_dir: &'a str,
pub struct Cache {
pub cache_dir: String,
pub use_cache: bool,
}
impl<'a> Default for Cache<'a> {
fn default() -> Cache<'a> {
impl Default for Cache {
fn default() -> Cache {
Cache {
cache_dir: DEFAULT_CACHE_DIR,
cache_dir: String::from(DEFAULT_CACHE_DIR),
use_cache: true,
}
}
}
impl<'a> Cache<'a> {
impl Cache {
/**
* Create the required directories for the cache
*/
pub fn init(&self) -> Result<(), Error> {
match create_dir_all(self.cache_dir) {
match create_dir_all(&self.cache_dir) {
Ok(_) => {
let metadata_path_str = format!("{}/{}", self.cache_dir, CACHE_METADATA_FILE);
let metadata_path = Path::new(&metadata_path_str);
@ -97,8 +97,8 @@ impl<'a> Cache<'a> {
// If they match, continue
if current_metadata != loaded_metadata {
// If they don't wipe the cache to start new
match remove_dir_all(self.cache_dir) {
Ok(_) => match create_dir_all(self.cache_dir) {
match remove_dir_all(&self.cache_dir) {
Ok(_) => match create_dir_all(&self.cache_dir) {
Ok(_) => (),
Err(e) => println!("Error: {}", e),
},
@ -130,14 +130,14 @@ impl<'a> Cache<'a> {
* Clean the cache directory completely
*/
pub fn clean(&self) -> Result<(), Error> {
remove_dir_all(self.cache_dir)
remove_dir_all(&self.cache_dir)
}
/**
* Get the hash of the desired file and return it as a hex string
*/
pub fn get_file_hash(&self, path: &Path) -> Result<String, Error> {
let mut source = try!(File::open(&path));
let mut source = File::open(&path)?;
let mut buf: Vec<u8> = Vec::new();
source.read_to_end(&mut buf)?;
let mut sha1 = Sha1::new();
@ -170,8 +170,11 @@ impl<'a> Cache<'a> {
);
let cache_dir_str =
format!("{}/image/{}x{}/{}", self.cache_dir, size, size, &sha1[..10]);
// println!("Saving: {}", cache_path_str);
match create_dir_all(cache_dir_str) {
println!("Test");
println!("{}", DEFAULT_CACHE_DIR);
println!("{}", &self.cache_dir);
// println!("Saving: {}", &cache_path_str);
match create_dir_all(&cache_dir_str) {
Ok(_) => {
let file_path = Path::new(&cache_path_str);
match File::create(file_path) {
@ -185,10 +188,16 @@ impl<'a> Cache<'a> {
}
}
}
Err(e) => return Err(e),
Err(e) => {
println!("Unable to create file {:?}", file_path);
return Err(e);
}
}
}
Err(e) => return Err(e),
Err(e) => {
println!("Unable to create directory {:?}", &cache_dir_str);
return Err(e);
}
}
}
Err(e) => {
@ -284,7 +293,7 @@ impl<'a> Cache<'a> {
let desire_len = row_str.len() - 1;
row_str.truncate(desire_len);
row_str.push_str("\n");
try!(compressor.write(&row_str.into_bytes()));
compressor.write(&row_str.into_bytes())?;
}
let compressed_matrix = match compressor.finish() {
Ok(data) => data,
@ -293,8 +302,8 @@ impl<'a> Cache<'a> {
return Err(e);
}
};
try!(file.write(&compressed_matrix));
try!(file.flush());
file.write(&compressed_matrix)?;
file.flush()?;
}
Err(e) => {
return Err(e);
@ -370,20 +379,27 @@ impl<'a> Cache<'a> {
}
}
#[test]
fn test_get_file_hash() {
let target = "test_images/sample_01_large.jpg";
let target_path = Path::new(target);
let cache: Cache = Default::default();
let hash = cache.get_file_hash(&target_path);
match hash {
Ok(v) => {
println!("Hash: {}", v);
assert_eq!(v, String::from("4beb6f2d852b75a313863916a1803ebad13a3196"));
}
Err(e) => {
println!("Error: {:?}", e);
assert!(false);
#[cfg(test)]
mod tests {
use std::path::Path;
use cache::Cache;
#[test]
fn test_get_file_hash() {
let target = "test_images/sample_01_large.jpg";
let target_path = Path::new(target);
let cache: Cache = Default::default();
let hash = cache.get_file_hash(&target_path);
match hash {
Ok(v) => {
println!("Hash: {}", v);
assert_eq!(v, String::from("4beb6f2d852b75a313863916a1803ebad13a3196"));
}
Err(e) => {
println!("Error: {:?}", e);
assert!(false);
}
}
}
}

16
src/hash/ahash.rs

@ -6,24 +6,23 @@ use std::path::Path;
use cache::Cache;
use super::prepare_image;
use super::{HashType, PerceptualHash, Precision, PreparedImage};
use super::image::GenericImageView;
use super::prepare_image;
pub struct AHash<'a> {
prepared_image: Box<PreparedImage<'a>>,
pub struct AHash {
prepared_image: Box<PreparedImage>,
}
impl<'a> AHash<'a> {
pub fn new(path: &'a Path, precision: &Precision, cache: &Option<Cache>) -> Self {
impl AHash {
pub fn new(path: &Path, precision: &Precision, cache: &Option<Cache>) -> Self {
AHash {
prepared_image: Box::new(prepare_image(&path, &HashType::AHash, &precision, cache)),
}
}
}
impl<'a> PerceptualHash for AHash<'a> {
impl PerceptualHash for AHash {
/**
* Calculate the ahash of the provided prepared image.
*
@ -63,3 +62,6 @@ impl<'a> PerceptualHash for AHash<'a> {
}
}
}
#[cfg(test)]
mod tests {}

16
src/hash/dhash.rs

@ -6,24 +6,23 @@ use std::path::Path;
use cache::Cache;
use super::prepare_image;
use super::{HashType, PerceptualHash, Precision, PreparedImage};
use super::image::GenericImageView;
use super::prepare_image;
pub struct DHash<'a> {
prepared_image: Box<PreparedImage<'a>>,
pub struct DHash {
prepared_image: Box<PreparedImage>,
}
impl<'a> DHash<'a> {
pub fn new(path: &'a Path, precision: &Precision, cache: &Option<Cache>) -> Self {
impl DHash {
pub fn new(path: &Path, precision: &Precision, cache: &Option<Cache>) -> Self {
DHash {
prepared_image: Box::new(prepare_image(&path, &HashType::DHash, &precision, cache)),
}
}
}
impl<'a> PerceptualHash for DHash<'a> {
impl PerceptualHash for DHash {
/**
* Calculate the dhash of the provided prepared image
*
@ -69,3 +68,6 @@ impl<'a> PerceptualHash for DHash<'a> {
}
}
}
#[cfg(test)]
mod tests {}

107
src/hash/mod.rs

@ -6,12 +6,17 @@
extern crate dft;
extern crate image;
use self::image::FilterType;
use cache::Cache;
use std::f64;
use std::fmt;
use std::fmt::{Error, Formatter};
use std::path::Path;
use serde::export::fmt::Debug;
use cache::Cache;
use self::image::FilterType;
mod ahash;
mod dhash;
mod phash;
@ -42,30 +47,45 @@ const HAMMING_DISTANCE_SIMILARITY_LIMIT: u64 = 5u64;
/**
* Prepared image that can be used to generate hashes
*/
pub struct PreparedImage<'a> {
orig_path: &'a str,
pub struct PreparedImage {
orig_path: String,
image: Option<image::DynamicImage>,
}
/**
* Wraps the various perceptual hashes
*/
pub struct PerceptualHashes<'a> {
pub orig_path: &'a str,
#[derive(Debug)]
pub struct PerceptualHashes {
pub orig_path: String,
pub ahash: u64,
pub dhash: u64,
pub phash: u64,
pub phash: u64
}
impl<'a> PerceptualHashes<'a> {
pub fn similar(&self, other: &'a PerceptualHashes<'a>) -> bool {
impl PartialEq for PerceptualHashes {
fn eq(&self, other: &Self) -> bool {
return self.ahash == other.ahash
&& self.dhash == other.dhash
&& self.phash == other.phash;
}
fn ne(&self, other: &Self) -> bool {
return self.ahash != other.ahash
|| self.dhash != other.dhash
|| self.phash != other.phash;
}
}
impl PerceptualHashes {
pub fn similar(&self, other: &PerceptualHashes) -> bool {
if self.orig_path != other.orig_path
&& calculate_hamming_distance(self.ahash, other.ahash)
<= HAMMING_DISTANCE_SIMILARITY_LIMIT
<= HAMMING_DISTANCE_SIMILARITY_LIMIT
&& calculate_hamming_distance(self.dhash, other.dhash)
<= HAMMING_DISTANCE_SIMILARITY_LIMIT
<= HAMMING_DISTANCE_SIMILARITY_LIMIT
&& calculate_hamming_distance(self.phash, other.phash)
<= HAMMING_DISTANCE_SIMILARITY_LIMIT
<= HAMMING_DISTANCE_SIMILARITY_LIMIT
{
true
} else {
@ -81,7 +101,7 @@ impl<'a> PerceptualHashes<'a> {
* Medium aims for 64 bit precision
* High aims for 128 bit precision
*/
#[allow(dead_code)]
#[derive(Copy, Clone)]
pub enum Precision {
Low,
Medium,
@ -89,7 +109,6 @@ pub enum Precision {
}
// Get the size of the required image
//
impl Precision {
fn get_size(&self) -> u32 {
match *self {
@ -103,6 +122,7 @@ impl Precision {
/**
* Types of hashes supported
*/
#[derive(Copy, Clone)]
pub enum HashType {
AHash,
DHash,
@ -128,7 +148,7 @@ pub trait PerceptualHash {
// Functions //
/**
* Resonsible for parsing a path, converting an image and package it to be
* Responsible for parsing a path, converting an image and package it to be
* hashed.
*
* # Arguments
@ -141,12 +161,12 @@ pub trait PerceptualHash {
* A PreparedImage struct with the required information for performing hashing
*
*/
pub fn prepare_image<'a>(
path: &'a Path,
pub fn prepare_image(
path: &Path,
hash_type: &HashType,
precision: &Precision,
cache: &Option<Cache>,
) -> PreparedImage<'a> {
) -> PreparedImage {
let image_path = path.to_str().unwrap();
let size: u32 = match *hash_type {
HashType::PHash => precision.get_size() * 4,
@ -154,10 +174,10 @@ pub fn prepare_image<'a>(
};
// Check if we have the already converted image in a cache and use that if possible.
match *cache {
Some(ref c) => {
match c.get_image_from_cache(&path, size) {
Some(ref cache) => {
match cache.get_image_from_cache(&path, size) {
Some(image) => PreparedImage {
orig_path: &*image_path,
orig_path: String::from(&*image_path),
image: Some(image),
},
None => {
@ -165,7 +185,7 @@ pub fn prepare_image<'a>(
// Oh, and save it in a cache
match processed_image.image {
Some(ref image) => {
match c.put_image_in_cache(&path, size, &image) {
match cache.put_image_in_cache(&path, size, &image) {
Ok(_) => {}
Err(e) => println!("Unable to store image in cache. {}", e),
};
@ -197,7 +217,7 @@ fn process_image(image_path: &str, size: u32) -> PreparedImage {
}
};
PreparedImage {
orig_path: &*image_path,
orig_path: String::from(&*image_path),
image,
}
}
@ -205,8 +225,8 @@ fn process_image(image_path: &str, size: u32) -> PreparedImage {
/**
* Get a specific HashType hash
*/
pub fn get_perceptual_hash<'a>(
path: &'a Path,
pub fn get_perceptual_hash(
path: &Path,
precision: &Precision,
hash_type: &HashType,
cache: &Option<Cache>,
@ -221,20 +241,20 @@ pub fn get_perceptual_hash<'a>(
/**
* Get all perceptual hashes for an image
*/
pub fn get_perceptual_hashes<'a>(
path: &'a Path,
pub fn get_perceptual_hashes(
path: &Path,
precision: &Precision,
cache: &Option<Cache>,
) -> PerceptualHashes<'a> {
) -> PerceptualHashes {
let image_path = path.to_str().unwrap();
let ahash = ahash::AHash::new(&path, &precision, &cache).get_hash(&cache);
let dhash = dhash::DHash::new(&path, &precision, &cache).get_hash(&cache);
let phash = phash::PHash::new(&path, &precision, &cache).get_hash(&cache);
PerceptualHashes {
orig_path: &*image_path,
ahash: ahash,
dhash: dhash,
phash: phash,
orig_path: String::from(&*image_path),
ahash,
dhash,
phash,
}
}
@ -248,3 +268,26 @@ pub fn calculate_hamming_distance(hash1: u64, hash2: u64) -> u64 {
// the number of 1's in the difference to determine the hamming distance
(hash1 ^ hash2).count_ones() as u64
}
#[cfg(test)]
mod tests {
use hash::calculate_hamming_distance;
#[test]
fn test_no_hamming_distance() {
let hamming_distance = calculate_hamming_distance(0, 0);
assert_eq!(hamming_distance, 0);
}
#[test]
fn test_one_hamming_distance() {
let hamming_distance = calculate_hamming_distance(0, 1);
assert_eq!(hamming_distance, 1);
}
#[test]
fn test_two_hamming_distance() {
let hamming_distance = calculate_hamming_distance(0, 3);
assert_eq!(hamming_distance, 2);
}
}

109
src/hash/phash.rs

@ -6,25 +6,25 @@ use std::path::Path;
use cache::Cache;
use super::{HashType, PerceptualHash, Precision, PreparedImage};
use super::dft;
use super::dft::Transform;
use super::image::{DynamicImage, GenericImageView, Pixel};
use super::prepare_image;
use super::{HashType, PerceptualHash, Precision, PreparedImage};
pub struct PHash<'a> {
prepared_image: Box<PreparedImage<'a>>,
pub struct PHash {
prepared_image: Box<PreparedImage>,
}
impl<'a> PHash<'a> {
pub fn new(path: &'a Path, precision: &Precision, cache: &Option<Cache>) -> Self {
impl PHash {
pub fn new(path: &Path, precision: &Precision, cache: &Option<Cache>) -> Self {
PHash {
prepared_image: Box::new(prepare_image(&path, &HashType::PHash, &precision, cache)),
}
}
}
impl<'a> PerceptualHash for PHash<'a> {
impl PerceptualHash for PHash {
/**
* Calculate the phash of the provided prepared image
*
@ -41,18 +41,18 @@ impl<'a> PerceptualHash for PHash<'a> {
// Get 2d data to 2d FFT/DFT
// Either from the cache or calculate it
// Pretty fast already, so caching doesn't make a huge difference
// Atleast compared to opening and processing the images
// At least compared to opening and processing the images
let data_matrix: Vec<Vec<f64>> = match *cache {
Some(ref c) => {
match c.get_matrix_from_cache(
&Path::new(self.prepared_image.orig_path),
&Path::new(&self.prepared_image.orig_path),
width as u32,
) {
Some(matrix) => matrix,
None => {
let matrix = create_data_matrix(width, height, &image);
match c.put_matrix_in_cache(
&Path::new(self.prepared_image.orig_path),
&Path::new(&self.prepared_image.orig_path),
width as u32,
&matrix,
) {
@ -117,11 +117,11 @@ fn create_data_matrix(width: u32, height: u32, image: &DynamicImage) -> Vec<Vec<
data_matrix
}
// Use a 1D DFT to cacluate the 2D DFT.
// Use a 1D DFT to calcuate the 2D DFT.
//
// This is achieved by calculating the DFT for each row, then calculating the
// DFT for each column of DFT row data. This means that a 32x32 image with have
// 1024 1D DFT operations performed on it. (Slightly caclulation intensive)
// 1024 1D DFT operations performed on it. (Slightly calculation intensive)
//
// This operation is in place on the data in the provided vector
//
@ -189,44 +189,51 @@ fn round_float(f: f64) -> f64 {
}
}
#[test]
fn test_2d_dft() {
let mut test_matrix: Vec<Vec<f64>> = Vec::new();
test_matrix.push(vec![1f64, 1f64, 1f64, 3f64]);
test_matrix.push(vec![1f64, 2f64, 2f64, 1f64]);
test_matrix.push(vec![1f64, 2f64, 2f64, 1f64]);
test_matrix.push(vec![3f64, 1f64, 1f64, 1f64]);
println!("{:?}", test_matrix[0]);
println!("{:?}", test_matrix[1]);
println!("{:?}", test_matrix[2]);
println!("{:?}", test_matrix[3]);
println!("Performing 2d DFT");
calculate_2d_dft(&mut test_matrix);
println!("{:?}", test_matrix[0]);
println!("{:?}", test_matrix[1]);
println!("{:?}", test_matrix[2]);
println!("{:?}", test_matrix[3]);
assert!(test_matrix[0][0] == 24_f64);
assert!(test_matrix[0][1] == 0_f64);
assert!(test_matrix[0][2] == 0_f64);
assert!(test_matrix[0][3] == 0_f64);
assert!(test_matrix[1][0] == 0_f64);
assert!(test_matrix[1][1] == 0_f64);
assert!(test_matrix[1][2] == -2_f64);
assert!(test_matrix[1][3] == 2_f64);
assert!(test_matrix[2][0] == 0_f64);
assert!(test_matrix[2][1] == -2_f64);
assert!(test_matrix[2][2] == -4_f64);
assert!(test_matrix[2][3] == -2_f64);
assert!(test_matrix[3][0] == 0_f64);
assert!(test_matrix[3][1] == 2_f64);
assert!(test_matrix[3][2] == -2_f64);
assert!(test_matrix[3][3] == 0_f64);
#[cfg(test)]
mod tests {
use hash::phash::calculate_2d_dft;
#[test]
fn test_2d_dft() {
let mut test_matrix: Vec<Vec<f64>> = Vec::new();
test_matrix.push(vec![1f64, 1f64, 1f64, 3f64]);
test_matrix.push(vec![1f64, 2f64, 2f64, 1f64]);
test_matrix.push(vec![1f64, 2f64, 2f64, 1f64]);
test_matrix.push(vec![3f64, 1f64, 1f64, 1f64]);
println!("2d matrix before DFT");
println!("{:?}", test_matrix[0]);
println!("{:?}", test_matrix[1]);
println!("{:?}", test_matrix[2]);
println!("{:?}", test_matrix[3]);
println!("Performing 2d DFT");
calculate_2d_dft(&mut test_matrix);
println!("2d matrix after DFT");
println!("{:?}", test_matrix[0]);
println!("{:?}", test_matrix[1]);
println!("{:?}", test_matrix[2]);
println!("{:?}", test_matrix[3]);
assert_eq!(test_matrix[0][0], 24_f64);
assert_eq!(test_matrix[0][1], 0_f64);
assert_eq!(test_matrix[0][2], 0_f64);
assert_eq!(test_matrix[0][3], 0_f64);
assert_eq!(test_matrix[1][0], 0_f64);
assert_eq!(test_matrix[1][1], 0_f64);
assert_eq!(test_matrix[1][2], -2_f64);
assert_eq!(test_matrix[1][3], 2_f64);
assert_eq!(test_matrix[2][0], 0_f64);
assert_eq!(test_matrix[2][1], -2_f64);
assert_eq!(test_matrix[2][2], -4_f64);
assert_eq!(test_matrix[2][3], -2_f64);
assert_eq!(test_matrix[3][0], 0_f64);
assert_eq!(test_matrix[3][1], 2_f64);
assert_eq!(test_matrix[3][2], -2_f64);
assert_eq!(test_matrix[3][3], 0_f64);
}
}

434
src/lib.rs

@ -8,6 +8,7 @@
extern crate libc;
extern crate rustc_serialize;
extern crate serde;
#[cfg(feature = "bench")]
extern crate test;
@ -20,20 +21,20 @@ pub mod cache;
pub mod hash;
#[repr(C)]
pub struct PIHash<'a> {
cache: Option<Cache<'a>>,
pub struct PIHash {
cache: Option<Cache>,
}
impl<'a> PIHash<'a> {
impl PIHash {
/**
* 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> {
pub fn new(cache_path: Option<&str>) -> PIHash {
match cache_path {
Some(path) => {
let cache = Cache {
cache_dir: path,
cache_dir: String::from(path),
use_cache: true,
};
match cache.init() {
@ -57,7 +58,7 @@ impl<'a> PIHash<'a> {
hash::get_perceptual_hash(&path, &precision, &hash_type, &self.cache)
}
pub fn get_phashes(&self, path: &'a Path) -> hash::PerceptualHashes {
pub fn get_pihashes(&self, path: &Path) -> hash::PerceptualHashes {
hash::get_perceptual_hashes(&path, &hash::Precision::Medium, &self.cache)
}
@ -159,24 +160,24 @@ pub struct PIHashes {
}
#[no_mangle]
pub extern "C" fn ext_get_phashes(lib: &PIHash, path_char: *const libc::c_char) -> *mut PIHashes {
pub extern "C" fn ext_get_pihashes(lib: &PIHash, path_char: *const libc::c_char) -> *mut PIHashes {
unsafe {
let path_str = CStr::from_ptr(path_char);
let image_path = get_str_from_cstr(path_str);
let path = Path::new(&image_path);
let phashes = lib.get_phashes(path);
let pihashes = lib.get_pihashes(path);
Box::into_raw(Box::new(PIHashes {
ahash: phashes.ahash,
dhash: phashes.dhash,
phash: phashes.phash,
ahash: pihashes.ahash,
dhash: pihashes.dhash,
phash: pihashes.phash,
}))
}
}
#[no_mangle]
pub extern "C" fn ext_free_phashes(raw_phashes: *const libc::c_void) {
pub extern "C" fn ext_free_pihashes(raw_pihashes: *const libc::c_void) {
unsafe {
drop(Box::from_raw(raw_phashes as *mut PIHashes));
drop(Box::from_raw(raw_pihashes as *mut PIHashes));
}
}
@ -213,10 +214,14 @@ mod tests {
use cache;
use hash;
use hash::{PerceptualHash, PerceptualHashes};
use super::PIHash;
#[cfg(feature = "bench")]
use super::test::Bencher;
use super::PIHash;
thread_local!(static LIB: PIHash = PIHash::new(Some(cache::DEFAULT_CACHE_DIR)));
thread_local!(static NO_CACHE_LIB: PIHash = PIHash::new(None));
#[test]
fn test_can_get_test_images() {
@ -247,7 +252,7 @@ mod tests {
* Updated test function. Assumes 3 images to a set and no hamming distances.
* We don't need to confirm that the hamming distance calculation works in these tests.
*/
fn test_imageset_hash(
fn test_image_set_hash(
hash_type: hash::HashType,
hash_precision: hash::Precision,
max_hamming_distance: u64,
@ -257,7 +262,7 @@ mod tests {
) {
let mut hashes: [u64; 3] = [0; 3];
for index in 0..image_paths.len() {
// println!("{}, {:?}", index, image_paths[index]);
// println!("{}, {:?}", index, image_paths[index]);
let image_path = image_paths[index];
let calculated_hash = lib.get_perceptual_hash(&image_path, &hash_precision, &hash_type);
println!(
@ -288,37 +293,116 @@ mod tests {
}
}
#[test]
fn test_confirm_ahash_results() {
// Prep_library
let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR));
let no_cache_lib = PIHash::new(None);
/**
* Test image set with and without caching
*/
fn test_image_set(
hash_type: hash::HashType,
hash_precision: hash::Precision,
max_hamming_distance: u64,
image_paths: [&Path; 3],
image_hashes: [u64; 3],
) {
LIB.with(|lib| {
test_image_set_hash(
hash_type,
hash_precision,
max_hamming_distance,
image_paths,
image_hashes,
lib,
);
});
NO_CACHE_LIB.with(|lib| {
test_image_set_hash(
hash_type,
hash_precision,
max_hamming_distance,
image_paths,
image_hashes,
lib,
);
});
}
// Sample_01 tests
/**
* Updated test function. Assumes 3 images to a set and no hamming distances.
* We don't need to confirm that the hamming distance calculation works in these tests.
*/
fn test_images_hashes(
image_hashes: &[PerceptualHashes],
lib: &PIHash,
) {
let mut hashes = vec![];
for index in 0..image_hashes.len() {
// println!("{}, {:?}", index, image_paths[index]);
let image_path = Path::new(&image_hashes[index].orig_path);
let calculated_hash = lib.get_pihashes(&image_path);
println!(
"Image hashes expected: [{:?}] actual: [{:?}]",
image_hashes[index],
calculated_hash
);
hashes.push(calculated_hash);
}
for index in 0..image_hashes.len() {
assert_eq!(hashes[index], image_hashes[index]);
}
//
// 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);
// }
// }
// }
}
/**
* Test images with and without caching
*/
fn test_images(image_hashes: &[PerceptualHashes]) {
LIB.with(|lib| {
test_images_hashes(
&image_hashes,
lib,
);
});
NO_CACHE_LIB.with(|lib| {
test_images_hashes(
&image_hashes,
lib,
);
});
}
#[test]
fn test_confirm_ahash_results_sample_01() {
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_small.jpg"),
];
let sample_01_hashes: [u64; 3] = [857051991849750, 857051991849750, 857051991849750];
test_imageset_hash(
test_image_set(
hash::HashType::AHash,
hash::Precision::Medium,
0u64,
sample_01_images,
sample_01_hashes,
&lib,
);
test_imageset_hash(
hash::HashType::AHash,
hash::Precision::Medium,
0u64,
sample_01_images,
sample_01_hashes,
&no_cache_lib,
);
}
// Sample_02 tests
#[test]
fn test_confirm_ahash_results_sample_02() {
let sample_02_images: [&Path; 3] = [
&Path::new("./test_images/sample_02_large.jpg"),
&Path::new("./test_images/sample_02_medium.jpg"),
@ -329,24 +413,17 @@ mod tests {
18446744073441116160,
18446744073441116160,
];
test_imageset_hash(
hash::HashType::AHash,
hash::Precision::Medium,
0u64,
sample_02_images,
sample_02_hashes,
&lib,
);
test_imageset_hash(
test_image_set(
hash::HashType::AHash,
hash::Precision::Medium,
0u64,
sample_02_images,
sample_02_hashes,
&no_cache_lib,
);
}
// Sample_03 tests
#[test]
fn test_confirm_ahash_results_sample_03() {
let sample_03_images: [&Path; 3] = [
&Path::new("./test_images/sample_03_large.jpg"),
&Path::new("./test_images/sample_03_medium.jpg"),
@ -354,24 +431,17 @@ mod tests {
];
let sample_03_hashes: [u64; 3] =
[135670932300497406, 135670932300497406, 135670932300497406];
test_imageset_hash(
hash::HashType::AHash,
hash::Precision::Medium,
0u64,
sample_03_images,
sample_03_hashes,
&lib,
);
test_imageset_hash(
test_image_set(
hash::HashType::AHash,
hash::Precision::Medium,
0u64,
sample_03_images,
sample_03_hashes,
&no_cache_lib,
);
}
// Sample_04 tests
#[test]
fn test_confirm_ahash_results_sample_04() {
let sample_04_images: [&Path; 3] = [
&Path::new("./test_images/sample_04_large.jpg"),
&Path::new("./test_images/sample_04_medium.jpg"),
@ -382,34 +452,20 @@ mod tests {
18446460933225054208,
18446460933225054208,
];
test_imageset_hash(
hash::HashType::AHash,
hash::Precision::Medium,
0u64,
sample_04_images,
sample_04_hashes,
&lib,
);
test_imageset_hash(
hash::HashType::AHash,
hash::Precision::Medium,
0u64,
sample_04_images,
sample_04_hashes,
&no_cache_lib,
);
// Clean_Cache
// super::teardown();
LIB.with(|lib| {
test_image_set_hash(
hash::HashType::AHash,
hash::Precision::Medium,
0u64,
sample_04_images,
sample_04_hashes,
lib,
);
});
}
#[test]
fn test_confirm_dhash_results() {
// Prep_library
let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR));
let no_cache_lib = PIHash::new(None);
// Sample_01 tests
fn test_confirm_dhash_results_sample_01() {
let sample_01_images: [&Path; 3] = [
&Path::new("./test_images/sample_01_large.jpg"),
&Path::new("./test_images/sample_01_medium.jpg"),
@ -420,24 +476,20 @@ mod tests {
3404580580803739582,
3404580580803739582,
];
test_imageset_hash(
hash::HashType::DHash,
hash::Precision::Medium,
0u64,
sample_01_images,
sample_01_hashes,
&lib,
);
test_imageset_hash(
hash::HashType::DHash,
hash::Precision::Medium,
0u64,
sample_01_images,
sample_01_hashes,
&no_cache_lib,
);
LIB.with(|lib| {
test_image_set_hash(
hash::HashType::DHash,
hash::Precision::Medium,
0u64,
sample_01_images,
sample_01_hashes,
lib,
);
});
}
// Sample_02 tests
#[test]
fn test_confirm_dhash_results_sample_02() {
let sample_02_images: [&Path; 3] = [
&Path::new("./test_images/sample_02_large.jpg"),
&Path::new("./test_images/sample_02_medium.jpg"),
@ -448,24 +500,20 @@ mod tests {
14726771606135242753,
14726771606135242753,
];
test_imageset_hash(
hash::HashType::DHash,
hash::Precision::Medium,
0u64,
sample_02_images,
sample_02_hashes,
&lib,
);
test_imageset_hash(
hash::HashType::DHash,
hash::Precision::Medium,
0u64,
sample_02_images,
sample_02_hashes,
&no_cache_lib,
);
LIB.with(|lib| {
test_image_set_hash(
hash::HashType::DHash,
hash::Precision::Medium,
0u64,
sample_02_images,
sample_02_hashes,
lib,
);
});
}
// Sample_03 tests
#[test]
fn test_confirm_dhash_results_sample_03() {
let sample_03_images: [&Path; 3] = [
&Path::new("./test_images/sample_03_large.jpg"),
&Path::new("./test_images/sample_03_medium.jpg"),
@ -473,24 +521,20 @@ mod tests {
];
let sample_03_hashes: [u64; 3] =
[144115181601817086, 144115181601817086, 144115181601817086];
test_imageset_hash(
hash::HashType::DHash,
hash::Precision::Medium,
0u64,
sample_03_images,
sample_03_hashes,
&lib,
);
test_imageset_hash(
hash::HashType::DHash,
hash::Precision::Medium,
0u64,
sample_03_images,
sample_03_hashes,
&no_cache_lib,
);
LIB.with(|lib| {
test_image_set_hash(
hash::HashType::DHash,
hash::Precision::Medium,
0u64,
sample_03_images,
sample_03_hashes,
lib,
);
});
}
// Sample_04 tests
#[test]
fn test_confirm_dhash_results_sample_04() {
let sample_04_images: [&Path; 3] = [
&Path::new("./test_images/sample_04_large.jpg"),
&Path::new("./test_images/sample_04_medium.jpg"),
@ -501,58 +545,37 @@ mod tests {
18374262188442386433,
18374262188442386433,
];
test_imageset_hash(
hash::HashType::DHash,
hash::Precision::Medium,
0u64,
sample_04_images,
sample_04_hashes,
&lib,
);
test_imageset_hash(
hash::HashType::DHash,
hash::Precision::Medium,
0u64,
sample_04_images,
sample_04_hashes,
&no_cache_lib,
);
// Clean_Cache
// super::teardown();
LIB.with(|lib| {
test_image_set_hash(
hash::HashType::DHash,
hash::Precision::Medium,
0u64,
sample_04_images,
sample_04_hashes,
lib,
);
});
}
#[test]
fn test_confirm_phash_results() {
// Prep_library
let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR));
let no_cache_lib = PIHash::new(None);
// Sample_01 tests
fn test_confirm_phash_results_sample_01() {
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_small.jpg"),
];
let sample_01_hashes: [u64; 3] = [72357778504597504, 72357778504597504, 72357778504597504];
test_imageset_hash(
hash::HashType::PHash,
hash::Precision::Medium,
0u64,
sample_01_images,
sample_01_hashes,
&lib,
);
test_imageset_hash(
test_image_set(
hash::HashType::PHash,
hash::Precision::Medium,
0u64,
sample_01_images,
sample_01_hashes,
&no_cache_lib,
);
}
// Sample_02 tests
#[test]
fn test_confirm_phash_results_sample_02() {
let sample_02_images: [&Path; 3] = [
&Path::new("./test_images/sample_02_large.jpg"),
&Path::new("./test_images/sample_02_medium.jpg"),
@ -563,24 +586,17 @@ mod tests {
5332332327550844928,
5332332327550844928,
];
test_imageset_hash(
test_image_set(
hash::HashType::PHash,
hash::Precision::Medium,
0u64,
sample_02_images,
sample_02_hashes,
&lib,
);
test_imageset_hash(
hash::HashType::PHash,
hash::Precision::Medium,
0u64,
sample_02_images,
sample_02_hashes,
&no_cache_lib,
);
}
// Sample_03 tests
#[test]
fn test_confirm_phash_results_sample_03() {
let sample_03_images: [&Path; 3] = [
&Path::new("./test_images/sample_03_large.jpg"),
&Path::new("./test_images/sample_03_medium.jpg"),
@ -591,24 +607,17 @@ mod tests {
6917529027641081856,
6917529027641081856,
];
test_imageset_hash(
test_image_set(
hash::HashType::PHash,
hash::Precision::Medium,
0u64,
sample_03_images,
sample_03_hashes,
&lib,
);
test_imageset_hash(
hash::HashType::PHash,
hash::Precision::Medium,
0u64,
sample_03_images,
sample_03_hashes,
&no_cache_lib,
);
}
// Sample_04 tests
#[test]
fn test_confirm_phash_results_sample_04() {
let sample_04_images: [&Path; 3] = [
&Path::new("./test_images/sample_04_large.jpg"),
&Path::new("./test_images/sample_04_medium.jpg"),
@ -619,25 +628,44 @@ mod tests {
10997931646002397184,
10997931646002397184,
];
test_imageset_hash(
test_image_set(
hash::HashType::PHash,
hash::Precision::Medium,
0u64,
sample_04_images,
sample_04_hashes,
&lib,
);
test_imageset_hash(
hash::HashType::PHash,
hash::Precision::Medium,
0u64,
sample_04_images,
sample_04_hashes,
&no_cache_lib,
);
}
// Clean_Cache
// super::teardown();
#[test]
fn test_confirm_pihash_results() {
let sample_hashes: [PerceptualHashes; 4] = [
PerceptualHashes {
orig_path: "./test_images/sample_01_large.jpg".to_string(),
ahash: 857051991849750,
dhash: 3404580580803739582,
phash: 72357778504597504,
},
PerceptualHashes {
orig_path: "./test_images/sample_02_large.jpg".to_string(),
ahash: 18446744073441116160,
dhash: 14726771606135242753,
phash: 5332332327550844928,
},
PerceptualHashes {
orig_path: "./test_images/sample_03_large.jpg".to_string(),
ahash: 135670932300497406,
dhash: 144115181601817086,
phash: 6917529027641081856,
},
PerceptualHashes {
orig_path: "./test_images/sample_04_large.jpg".to_string(),
ahash: 18446460933225054208,
dhash: 18374262188442386433,
phash: 10997931646002397184,
}
];
test_images(&sample_hashes);
}
#[cfg(feature = "bench")]
@ -647,7 +675,7 @@ mod tests {
let lib = PIHash::new(Some(cache::DEFAULT_CACHE_DIR));
// 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 pihashes so that the matrices are pulled from cache as well
lib.get_perceptual_hash(
&Path::new("./test_images/sample_01_large.jpg"),
&hash::Precision::Medium,
@ -655,7 +683,6 @@ mod tests {
);
bench.iter(|| {
// Sample_01 Bench
lib.get_perceptual_hash(
&Path::new("./test_images/sample_01_large.jpg"),
&hash::Precision::Medium,
@ -671,7 +698,6 @@ mod tests {
let lib = PIHash::new(None);
bench.iter(|| {
// Sample_01 Bench
lib.get_perceptual_hash(
&Path::new("./test_images/sample_01_large.jpg"),
&hash::Precision::Medium,

31
src/main.rs

@ -9,9 +9,10 @@ extern crate rustc_serialize;
#[macro_use]
extern crate serde_derive;
use docopt::Docopt;
use std::path::Path;
use docopt::Docopt;
// Getting the version information from cargo during compile time
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
@ -32,6 +33,7 @@ Options:
-a, --ahash Include an ahash calculation.
-d, --dhash Include an dhash calculation.
-p, --phash Include an phash calculation.
-n, --nocache Disable caching behavior.
";
#[derive(Debug, Deserialize)]
@ -42,6 +44,7 @@ struct Args {
flag_phash: bool,
arg_path: String,
arg_comparison: Vec<String>,
flag_nocache: bool,
}
fn main() {
@ -55,8 +58,14 @@ fn main() {
std::process::exit(0);
}
let cache = if args.flag_nocache {
None
} else {
Some(pihash::cache::DEFAULT_CACHE_DIR)
};
// Init the hashing library
let lib = pihash::PIHash::new(Some(pihash::cache::DEFAULT_CACHE_DIR));
let lib = pihash::PIHash::new(cache);
// println!("{:?}", args);
if args.arg_comparison.len() > 0 {
@ -72,10 +81,10 @@ fn main() {
));
}
let mut similar_images: Vec<&str> = Vec::new();
let mut similar_images: Vec<String> = Vec::new();
for comparison_hash in comparison_hashes {
if base_hash.similar(&comparison_hash) {
similar_images.push(&comparison_hash.orig_path);
similar_images.push(String::from(&comparison_hash.orig_path));
}
}
@ -106,11 +115,11 @@ fn flags_get_all_perceptual_hashes(args: &Args) -> bool {
|| (!args.flag_ahash && !args.flag_dhash && !args.flag_phash)
}
fn get_requested_perceptual_hashes<'a>(
fn get_requested_perceptual_hashes(
lib: &pihash::PIHash,
image_path: &'a Path,
image_path: &Path,
args: &Args,
) -> pihash::hash::PerceptualHashes<'a> {
) -> pihash::hash::PerceptualHashes {
let ahash = if args.flag_ahash || flags_get_all_perceptual_hashes(&args) {
lib.get_ahash(&image_path)
} else {
@ -130,9 +139,9 @@ fn get_requested_perceptual_hashes<'a>(
};
pihash::hash::PerceptualHashes {
orig_path: image_path.to_str().unwrap(),
ahash: ahash,
dhash: dhash,
phash: phash,
orig_path: String::from(image_path.to_str().unwrap()),
ahash,
dhash,
phash,
}
}
Loading…
Cancel
Save