Browse Source

Updated libraries to latest versions.

* Breaking change, new hashes are computed as a result of the underlying image library update
master
Drew Short 6 years ago
parent
commit
725f22f732
  1. 19
      Cargo.toml
  2. 53
      src/cache.rs
  3. 22
      src/hash/ahash.rs
  4. 30
      src/hash/dhash.rs
  5. 39
      src/hash/mod.rs
  6. 12
      src/hash/phash.rs
  7. 61
      src/lib.rs
  8. 13
      src/main.rs

19
Cargo.toml

@ -1,6 +1,6 @@
[package]
name = "pihash"
version = "0.3.6"
version = "0.4.0"
authors = ["Drew Short <warrick@sothr.com>"]
description = "A simple library for generating perceptual hashes for images and comparing images based on their perceptual hashes."
repository = "https://github.com/warricksothr/Perceptual-Image-Hashing/"
@ -20,11 +20,14 @@ default = []
bench = []
[dependencies]
libc = "0.2.20"
libc = "0.2.36"
rustc-serialize = "0.3.22"
dft = "0.5.4"
image = "0.13.0"
num = "0.1.36"
docopt = "0.7.0"
flate2 = "0.2.19"
sha1 = "0.2.0"
dft = "0.5.5"
image = "0.18.0"
num = "0.1.42"
docopt = "0.8.3"
serde = "1.0"
serde_derive = "1.0"
flate2 = "1.0.1"
sha1 = "0.6.0"

53
src/cache.rs

@ -3,22 +3,23 @@
// Licensed under the MIT license<LICENSE-MIT or http://opensource.org/licenses/MIT>.
// This file may not be copied, modified, or distributed except according to those terms.
extern crate num;
extern crate flate2;
extern crate image;
extern crate num;
extern crate sha1;
use self::image::ImageBuffer;
use self::sha1::Sha1;
use self::flate2::Compression;
use self::flate2::write::ZlibEncoder;
use self::flate2::read::ZlibDecoder;
use std::str::FromStr;
use std::path::Path;
use std::fs::{File, create_dir_all, remove_dir_all};
use std::io::{Read, Error, Write};
use self::flate2::write::ZlibEncoder;
use self::image::DynamicImage;
use self::sha1::Sha1;
use std::default::Default;
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;
use std::result::Result;
use std::str::FromStr;
use super::rustc_serialize::json;
pub const DEFAULT_CACHE_DIR: &'static str = "./.hash_cache";
@ -150,7 +151,7 @@ impl<'a> Cache<'a> {
pub fn put_image_in_cache(&self,
path: &Path,
size: u32,
image: &ImageBuffer<image::Luma<u8>, Vec<u8>>)
image: &DynamicImage)
-> Result<bool, Error> {
let hash = self.get_file_hash(&path);
match hash {
@ -165,17 +166,21 @@ impl<'a> Cache<'a> {
// println!("Saving: {}", cache_path_str);
match create_dir_all(cache_dir_str) {
Ok(_) => {
let cached_path = Path::new(&cache_path_str);
// Save the file into the cache
match image.save(cached_path) {
Ok(_) => {}
Err(e) => {
println!("Error: {}", e);
return Err(e);
match File::create(Path::new(&cache_path_str)) {
Ok(mut file) => {
// Save the file into the cache
match image.save(& mut file, image::ImageFormat::PNG) {
Ok(_) => {}
Err(e) => {
println ! ("Error: {}", e);
return Err(Error::new(ErrorKind::Other, e));
}
}
}
Err(e) => return Err(e),
}
}
Err(e) => println!("Error: {}", e),
Err(e) => return Err(e),
}
}
Err(e) => {
@ -192,7 +197,7 @@ impl<'a> Cache<'a> {
pub fn get_image_from_cache(&self,
path: &Path,
size: u32)
-> Option<ImageBuffer<image::Luma<u8>, Vec<u8>>> {
-> Option<DynamicImage> {
if self.use_cache {
let hash = self.get_file_hash(&path);
match hash {
@ -209,7 +214,7 @@ impl<'a> Cache<'a> {
match File::open(&cached_path) {
Ok(_) => {
let image = image::open(&cached_path).unwrap();
Some(image.to_luma())
Some(image)
}
// Don't really care here, it just means an existing cached
// file doesn't exist, or can't be read.
@ -251,7 +256,7 @@ impl<'a> Cache<'a> {
match File::create(&cached_path) {
Ok(mut file) => {
let mut compressor = ZlibEncoder::new(Vec::new(),
Compression::Default);
Compression::default());
for row in file_contents {
let mut row_str =
row.iter()
@ -322,10 +327,10 @@ impl<'a> Cache<'a> {
.trim()
.split("\n")
.map(|line| {
line.split(",")
.map(|f| f64::from_str(f).unwrap())
.collect()
})
line.split(",")
.map(|f| f64::from_str(f).unwrap())
.collect()
})
.collect();
Some(matrix)

22
src/hash/ahash.rs

@ -3,11 +3,13 @@
// Licensed under the MIT license<LICENSE-MIT or http://opensource.org/licenses/MIT>.
// This file may not be copied, modified, or distributed except according to those terms.
extern crate image;
use cache::Cache;
use self::image::GenericImage;
use std::path::Path;
use super::{HashType, PerceptualHash, Precision, PreparedImage};
use super::prepare_image;
use super::image::Pixel;
use std::path::Path;
use cache::Cache;
pub struct AHash<'a> {
prepared_image: Box<PreparedImage<'a>>,
@ -36,20 +38,16 @@ impl<'a> PerceptualHash for AHash<'a> {
// calculating the average pixel value
let mut total = 0u64;
for pixel in image.pixels() {
let channels = pixel.channels();
// println!("Pixel is: {}", channels[0]);
total += channels[0] as u64;
for (_, _, pixel) in image.pixels() {
total += pixel.data[0] as u64;
}
let mean = total / (width * height) as u64;
let mean = total / (height * width) as u64;
// println!("Mean for {} is {}", prepared_image.orig_path, mean);
// Calculating a hash based on the mean
let mut hash = 0u64;
for pixel in image.pixels() {
let channels = pixel.channels();
let pixel_sum = channels[0] as u64;
if pixel_sum >= mean {
for (_, _, pixel) in image.pixels() {
if pixel.data[0] as u64 >= mean {
hash |= 1;
// println!("Pixel {} is >= {} therefore {:b}", pixel_sum, mean, hash);
} else {

30
src/hash/dhash.rs

@ -2,12 +2,13 @@
//
// Licensed under the MIT license<LICENSE-MIT or http://opensource.org/licenses/MIT>.
// This file may not be copied, modified, or distributed except according to those terms.
extern crate image;
use cache::Cache;
use self::image::GenericImage;
use std::path::Path;
use super::{HashType, PerceptualHash, Precision, PreparedImage};
use super::prepare_image;
use super::image::Pixel;
use std::path::Path;
use cache::Cache;
pub struct DHash<'a> {
prepared_image: Box<PreparedImage<'a>>,
@ -32,29 +33,30 @@ impl<'a> PerceptualHash for DHash<'a> {
fn get_hash(&self, _: &Option<Cache>) -> u64 {
match self.prepared_image.image {
Some(ref image) => {
let first_pixel_val = image.pixels().nth(0).unwrap().channels()[0];
let last_pixel_val = image.pixels().last().unwrap().channels()[0];
let (_, _, first_pixel) = image.pixels().nth(0).unwrap();
let (_, _, last_pixel) = image.pixels().last().unwrap();
let first_pixel_value = first_pixel.data[0] as u64;
let last_pixel_value = last_pixel.data[0] as u64;
// Calculate the dhash
let mut previous_pixel_val = 0u64;
let mut previous_pixel_value = 0u64;
let mut hash = 0u64;
for (index, pixel) in image.pixels().enumerate() {
if index == 0 {
previous_pixel_val = pixel.channels()[0] as u64;
for (x, y, pixel) in image.pixels() {
if x == 0 && y == 0 {
previous_pixel_value = pixel.data[0] as u64;
continue;
}
let channels = pixel.channels();
let pixel_val = channels[0] as u64;
if pixel_val >= previous_pixel_val {
let pixel_val = pixel.data[0] as u64;
if pixel_val >= previous_pixel_value {
hash |= 1;
} else {
hash |= 0;
}
hash <<= 1;
previous_pixel_val = channels[0] as u64;
previous_pixel_value = first_pixel_value;
}
if first_pixel_val >= last_pixel_val {
if first_pixel_value >= last_pixel_value {
hash |= 1;
} else {
hash |= 0;

39
src/hash/mod.rs

@ -6,15 +6,16 @@
extern crate dft;
extern crate image;
use cache::Cache;
use self::image::FilterType;
use std::f64;
use std::path::Path;
use std::fmt;
mod ahash;
mod dhash;
mod phash;
use std::path::Path;
use std::f64;
use self::image::FilterType;
use cache::Cache;
// Constants //
// Used to get ranges for the precision of rounding floats
@ -43,7 +44,7 @@ const HAMMING_DISTANCE_SIMILARITY_LIMIT: u64 = 5u64;
*/
pub struct PreparedImage<'a> {
orig_path: &'a str,
image: Option<image::ImageBuffer<image::Luma<u8>, Vec<u8>>>,
image: Option<image::DynamicImage>,
}
/**
@ -59,12 +60,12 @@ pub struct PerceptualHashes<'a> {
impl<'a> PerceptualHashes<'a> {
pub fn similar(&self, other: &'a PerceptualHashes<'a>) -> bool {
if self.orig_path != other.orig_path &&
calculate_hamming_distance(self.ahash, other.ahash) <=
HAMMING_DISTANCE_SIMILARITY_LIMIT &&
calculate_hamming_distance(self.dhash, other.dhash) <=
HAMMING_DISTANCE_SIMILARITY_LIMIT &&
calculate_hamming_distance(self.phash, other.phash) <=
HAMMING_DISTANCE_SIMILARITY_LIMIT {
calculate_hamming_distance(self.ahash, other.ahash) <=
HAMMING_DISTANCE_SIMILARITY_LIMIT &&
calculate_hamming_distance(self.dhash, other.dhash) <=
HAMMING_DISTANCE_SIMILARITY_LIMIT &&
calculate_hamming_distance(self.phash, other.phash) <=
HAMMING_DISTANCE_SIMILARITY_LIMIT {
true
} else {
false
@ -107,6 +108,16 @@ pub enum HashType {
PHash,
}
impl fmt::Display for HashType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
HashType::AHash => write!(f, "AHash"),
HashType::DHash => write!(f, "DHash"),
HashType::PHash => write!(f, "PHash")
}
}
}
// Traits //
pub trait PerceptualHash {
@ -178,7 +189,7 @@ fn process_image<'a>(image_path: &'a str, size: u32) -> PreparedImage<'a> {
let image = match image::open(Path::new(image_path)) {
Ok(image) => {
let small_image = image.resize_exact(size, size, FilterType::Lanczos3);
Some(small_image.to_luma())
Some(small_image.grayscale())
}
Err(e) => {
println!("Error Processing Image [{}]: {} ", image_path, e);
@ -187,7 +198,7 @@ fn process_image<'a>(image_path: &'a str, size: u32) -> PreparedImage<'a> {
};
PreparedImage {
orig_path: &*image_path,
image: image,
image,
}
}

12
src/hash/phash.rs

@ -2,14 +2,16 @@
//
// Licensed under the MIT license<LICENSE-MIT or http://opensource.org/licenses/MIT>.
// This file may not be copied, modified, or distributed except according to those terms.
extern crate image;
use cache::Cache;
use self::image::{GenericImage, DynamicImage};
use std::path::Path;
use super::{HashType, PerceptualHash, Precision, PreparedImage};
use super::dft;
use super::dft::Transform;
use super::{HashType, PerceptualHash, Precision, PreparedImage};
use super::prepare_image;
use super::image::Pixel;
use std::path::Path;
use cache::Cache;
use super::prepare_image;
pub struct PHash<'a> {
prepared_image: Box<PreparedImage<'a>>,
@ -98,7 +100,7 @@ impl<'a> PerceptualHash for PHash<'a> {
fn create_data_matrix(width: usize,
height: usize,
image: &super::image::ImageBuffer<super::image::Luma<u8>, Vec<u8>>)
image: &DynamicImage)
-> Vec<Vec<f64>> {
let mut data_matrix: Vec<Vec<f64>> = Vec::new();
// Preparing the results

61
src/lib.rs

@ -230,7 +230,8 @@ mod tests {
for index in 0..image_paths.len() {
let image_path = image_paths[index];
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: [{}]",
hash_type,
image_path.to_str().unwrap(),
image_hashes[index],
calculated_hash);
@ -264,7 +265,7 @@ mod tests {
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, 857051992374038];
let sample_01_hashes: [u64; 3] = [7065306774709811078, 7065306774709811078, 7065306774172940166];
test_imageset_hash(hash::HashType::AHash,
hash::Precision::Medium,
1u64,
@ -276,12 +277,10 @@ mod tests {
let sample_02_images: [&Path; 3] = [&Path::new("./test_images/sample_02_large.jpg"),
&Path::new("./test_images/sample_02_medium.jpg"),
&Path::new("./test_images/sample_02_small.jpg")];
let sample_02_hashes: [u64; 3] = [18446744073441116160,
18446744073441116160,
18446744073441116160];
let sample_02_hashes: [u64; 3] = [18446744068986765312, 18446744069246812160, 18446744073541779456];
test_imageset_hash(hash::HashType::AHash,
hash::Precision::Medium,
1u64,
3u64,
sample_02_images,
sample_02_hashes,
&lib);
@ -290,8 +289,7 @@ mod tests {
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_small.jpg")];
let sample_03_hashes: [u64; 3] =
[135670932300497406, 135670932300497406, 135670932300497406];
let sample_03_hashes: [u64; 3] = [108649334536274430, 126663733045756414, 108649334536274430];
test_imageset_hash(hash::HashType::AHash,
hash::Precision::Medium,
1u64,
@ -303,12 +301,10 @@ mod tests {
let sample_04_images: [&Path; 3] = [&Path::new("./test_images/sample_04_large.jpg"),
&Path::new("./test_images/sample_04_medium.jpg"),
&Path::new("./test_images/sample_04_small.jpg")];
let sample_04_hashes: [u64; 3] = [18446460933090836480,
18446460933090836480,
18446460933090836480];
let sample_04_hashes: [u64; 3] = [18446460933225054208, 18446460933225054208, 18446460933225054208];
test_imageset_hash(hash::HashType::AHash,
hash::Precision::Medium,
1u64,
0u64,
sample_04_images,
sample_04_hashes,
&lib);
@ -326,12 +322,10 @@ mod tests {
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] = [7937395827556495926,
7937395827556495926,
7939647627370181174];
let sample_01_hashes: [u64; 3] = [18131474507607572478, 18131474507607572478, 18131474507607572478];
test_imageset_hash(hash::HashType::DHash,
hash::Precision::Medium,
1u64,
0u64,
sample_01_images,
sample_01_hashes,
&lib);
@ -340,12 +334,10 @@ mod tests {
let sample_02_images: [&Path; 3] = [&Path::new("./test_images/sample_02_large.jpg"),
&Path::new("./test_images/sample_02_medium.jpg"),
&Path::new("./test_images/sample_02_small.jpg")];
let sample_02_hashes: [u64; 3] = [11018273919551199541,
11009266719759587637,
11009847262435924277];
let sample_02_hashes: [u64; 3] = [10088065226894213121, 10088065226894213121, 10088065226894213121];
test_imageset_hash(hash::HashType::DHash,
hash::Precision::Medium,
3u64,
0u64,
sample_02_images,
sample_02_hashes,
&lib);
@ -354,11 +346,10 @@ mod tests {
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_small.jpg")];
let sample_03_hashes: [u64; 3] =
[262683193365159876, 225528496439353284, 225528496435158982];
let sample_03_hashes: [u64; 3] = [144115181601817086, 144115181601817086, 144115181601817086];
test_imageset_hash(hash::HashType::DHash,
hash::Precision::Medium,
4u64,
0u64,
sample_03_images,
sample_03_hashes,
&lib);
@ -367,9 +358,7 @@ mod tests {
let sample_04_images: [&Path; 3] = [&Path::new("./test_images/sample_04_large.jpg"),
&Path::new("./test_images/sample_04_medium.jpg"),
&Path::new("./test_images/sample_04_small.jpg")];
let sample_04_hashes: [u64; 3] = [14620651386429567209,
14620651386429567209,
14620651386429567209];
let sample_04_hashes: [u64; 3] = [18374262326015557633, 18374262326015557633, 18374262326283993089];
test_imageset_hash(hash::HashType::DHash,
hash::Precision::Medium,
1u64,
@ -390,10 +379,10 @@ mod tests {
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];
let sample_01_hashes: [u64; 3] = [72410537899606272, 72410537899606272, 72410537899606400];
test_imageset_hash(hash::HashType::PHash,
hash::Precision::Medium,
0u64,
1u64,
sample_01_images,
sample_01_hashes,
&lib);
@ -402,12 +391,10 @@ mod tests {
let sample_02_images: [&Path; 3] = [&Path::new("./test_images/sample_02_large.jpg"),
&Path::new("./test_images/sample_02_medium.jpg"),
&Path::new("./test_images/sample_02_small.jpg")];
let sample_02_hashes: [u64; 3] = [5332332327550844928,
5332332327550844928,
5332332327550844928];
let sample_02_hashes: [u64; 3] = [5620563253458370560, 5620562703702556672, 5620562703702556672];
test_imageset_hash(hash::HashType::PHash,
hash::Precision::Medium,
0u64,
1u64,
sample_02_images,
sample_02_hashes,
&lib);
@ -416,9 +403,7 @@ mod tests {
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_small.jpg")];
let sample_03_hashes: [u64; 3] = [6917529027641081856,
6917529027641081856,
6917529027641081856];
let sample_03_hashes: [u64; 3] = [6926536226895822848, 6926536226895822848, 6926536226895822848];
test_imageset_hash(hash::HashType::PHash,
hash::Precision::Medium,
0u64,
@ -430,12 +415,10 @@ mod tests {
let sample_04_images: [&Path; 3] = [&Path::new("./test_images/sample_04_large.jpg"),
&Path::new("./test_images/sample_04_medium.jpg"),
&Path::new("./test_images/sample_04_small.jpg")];
let sample_04_hashes: [u64; 3] = [10997931646002397184,
10997931646002397184,
10997931646002397184];
let sample_04_hashes: [u64; 3] = [11430286023502856200, 10997940459275288576, 11142055647351144448];
test_imageset_hash(hash::HashType::PHash,
hash::Precision::Medium,
0u64,
3u64,
sample_04_images,
sample_04_hashes,
&lib);

13
src/main.rs

@ -3,12 +3,14 @@
// Licensed under the MIT license<LICENSE-MIT or http://opensource.org/licenses/MIT>.
// This file may not be copied, modified, or distributed except according to those terms.
extern crate docopt;
extern crate pihash;
extern crate rustc_serialize;
extern crate docopt;
#[macro_use]
extern crate serde_derive;
use std::path::Path;
use docopt::Docopt;
use std::path::Path;
// Getting the version information from cargo during compile time
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
@ -32,7 +34,7 @@ Options:
-p, --phash Include an phash calculation.
";
#[derive(Debug, RustcDecodable)]
#[derive(Debug, Deserialize)]
struct Args {
flag_version: bool,
flag_ahash: bool,
@ -44,7 +46,7 @@ struct Args {
fn main() {
let args: Args = Docopt::new(USAGE)
.and_then(|d| d.decode())
.and_then(|d| d.deserialize())
.unwrap_or_else(|e| e.exit());
// Print version information and exit
@ -82,7 +84,6 @@ fn main() {
for similar_image in similar_images {
println!("{}", similar_image);
}
} else {
let image_path = Path::new(&args.arg_path);
let hashes = get_requested_perceptual_hashes(&lib, &image_path, &args);
@ -102,7 +103,7 @@ fn main() {
fn flags_get_all_perceptual_hashes(args: &Args) -> bool {
(args.flag_ahash && args.flag_dhash && args.flag_phash) ||
(!args.flag_ahash && !args.flag_dhash && !args.flag_phash)
(!args.flag_ahash && !args.flag_dhash && !args.flag_phash)
}
fn get_requested_perceptual_hashes<'a>(lib: &pihash::PIHash,

Loading…
Cancel
Save