From 725f22f732fd494dbac656c482bb7533b23e6016 Mon Sep 17 00:00:00 2001 From: Drew Short Date: Thu, 15 Feb 2018 23:23:54 -0600 Subject: [PATCH] Updated libraries to latest versions. * Breaking change, new hashes are computed as a result of the underlying image library update --- Cargo.toml | 19 ++++++++------- src/cache.rs | 53 +++++++++++++++++++++------------------- src/hash/ahash.rs | 22 ++++++++--------- src/hash/dhash.rs | 30 ++++++++++++----------- src/hash/mod.rs | 39 +++++++++++++++++++----------- src/hash/phash.rs | 12 ++++++---- src/lib.rs | 61 +++++++++++++++++------------------------------ src/main.rs | 13 +++++----- 8 files changed, 127 insertions(+), 122 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a45e911..432fceb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pihash" -version = "0.3.6" +version = "0.4.0" authors = ["Drew Short "] 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" + diff --git a/src/cache.rs b/src/cache.rs index ae27932..cf069f6 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -3,22 +3,23 @@ // Licensed under the MIT license. // 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, Vec>) + image: &DynamicImage) -> Result { 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, Vec>> { + -> Option { 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) diff --git a/src/hash/ahash.rs b/src/hash/ahash.rs index 277dd92..1a045f4 100644 --- a/src/hash/ahash.rs +++ b/src/hash/ahash.rs @@ -3,11 +3,13 @@ // Licensed under the MIT license. // 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>, @@ -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 { diff --git a/src/hash/dhash.rs b/src/hash/dhash.rs index 6f7ac1c..0ac2529 100644 --- a/src/hash/dhash.rs +++ b/src/hash/dhash.rs @@ -2,12 +2,13 @@ // // Licensed under the MIT license. // 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>, @@ -32,29 +33,30 @@ impl<'a> PerceptualHash for DHash<'a> { fn get_hash(&self, _: &Option) -> 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; diff --git a/src/hash/mod.rs b/src/hash/mod.rs index 9ab1900..4686cb6 100644 --- a/src/hash/mod.rs +++ b/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, Vec>>, + image: Option, } /** @@ -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, } } diff --git a/src/hash/phash.rs b/src/hash/phash.rs index 97da8a6..2f3202d 100644 --- a/src/hash/phash.rs +++ b/src/hash/phash.rs @@ -2,14 +2,16 @@ // // Licensed under the MIT license. // 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>, @@ -98,7 +100,7 @@ impl<'a> PerceptualHash for PHash<'a> { fn create_data_matrix(width: usize, height: usize, - image: &super::image::ImageBuffer, Vec>) + image: &DynamicImage) -> Vec> { let mut data_matrix: Vec> = Vec::new(); // Preparing the results diff --git a/src/lib.rs b/src/lib.rs index ba9e8ec..1982706 100644 --- a/src/lib.rs +++ b/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); diff --git a/src/main.rs b/src/main.rs index df1facf..5f00b4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,14 @@ // Licensed under the MIT license. // 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,