From 854813a97e744137e217e5531567bd755ce715aa Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Mon, 9 Sep 2019 15:00:45 +0200 Subject: [PATCH 1/7] Rustfmt --- src/lib.rs | 159 +++++++++++++++++++++++++++++------------------------ 1 file changed, 88 insertions(+), 71 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4389d00..eef4146 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ //! ```rust,no_run //! extern crate image; //! extern crate img_hash; -//! +//! //! use img_hash::{ImageHash, HashType}; //! //! fn main() { @@ -23,7 +23,7 @@ //! let image2 = image::open("image2.png").unwrap(); //! //! // These two lines produce hashes with 64 bits (8 ** 2), -//! // using the Gradient hash, a good middle ground between +//! // using the Gradient hash, a good middle ground between //! // the performance of Mean and the accuracy of DCT. //! let hash1 = ImageHash::hash(&image1, 8, HashType::Gradient); //! let hash2 = ImageHash::hash(&image2, 8, HashType::Gradient); @@ -46,7 +46,7 @@ extern crate image; extern crate rustc_serialize as serialize; -use serialize::base64::{ToBase64, FromBase64, STANDARD}; +use serialize::base64::{FromBase64, ToBase64, STANDARD}; // Needs to be fully qualified pub use serialize::base64::FromBase64Error; @@ -92,24 +92,32 @@ impl ImageHash { /// Calculate the Hamming distance between this and `other`. /// Equivalent to counting the 1-bits of the XOR of the two `BitVec`. - /// + /// /// Essential to determining the perceived difference between `self` and `other`. /// /// ###Panics /// If `self` and `other` have differing `bitv` lengths or `hash_type` values. pub fn dist(&self, other: &ImageHash) -> usize { - assert_eq!(self.hash_type, other.hash_type, - "Image hashes must use the same algorithm for proper comparison!"); - assert_eq!(self.bitv.len(), other.bitv.len(), - "Image hashes must be the same length for proper comparison!"); - - self.bitv.iter().zip(other.bitv.iter()) - .filter(|&(left, right)| left != right).count() + assert_eq!( + self.hash_type, other.hash_type, + "Image hashes must use the same algorithm for proper comparison!" + ); + assert_eq!( + self.bitv.len(), + other.bitv.len(), + "Image hashes must be the same length for proper comparison!" + ); + + self.bitv + .iter() + .zip(other.bitv.iter()) + .filter(|&(left, right)| left != right) + .count() } /// Calculate the Hamming distance between `self` and `other`, /// then normalize it to `[0, 1]`, as a fraction of the total bits. - /// + /// /// Roughly equivalent to the % difference between the two images, /// represented as a decimal. /// @@ -117,19 +125,20 @@ impl ImageHash { pub fn dist_ratio(&self, other: &ImageHash) -> f32 { self.dist(other) as f32 / self.size() as f32 } - /// Get the hash size of this image. Should be equal to the number of bits in the hash. - pub fn size(&self) -> u32 { self.bitv.len() as u32 } + pub fn size(&self) -> u32 { + self.bitv.len() as u32 + } /// Get the `HashType` that this `ImageHash` was created with. - pub fn hash_type(&self) -> HashType { self.hash_type } + pub fn hash_type(&self) -> HashType { + self.hash_type + } - /// Build a grayscale image using the bits of the hash, + /// Build a grayscale image using the bits of the hash, /// setting pixels to white (`0xff`) for `0` and black (`0x00`) for `1`. pub fn to_bytes(&self) -> Vec { - self.bitv.iter() - .map(|bit| (bit as u8) * 0xff) - .collect() + self.bitv.iter().map(|bit| (bit as u8) * 0xff).collect() } /// Create an `ImageHash` instance from the given Base64-encoded string. @@ -139,7 +148,7 @@ impl ImageHash { /// Does **not** preserve the internal value of `HashType::UserDCT`. /// ## Errors: /// Returns a FromBase64Error::InvalidBase64Length when trying to hash a zero-length string - pub fn from_base64(encoded_hash: &str) -> Result{ + pub fn from_base64(encoded_hash: &str) -> Result { let mut data = try!(encoded_hash.from_base64()); // The hash type should be the first bit of the hash if data.len() == 0 { @@ -147,7 +156,7 @@ impl ImageHash { } let hash_type = HashType::from_byte(data.remove(0)); - Ok(ImageHash{ + Ok(ImageHash { bitv: BitVec::from_bytes(&*data), hash_type: hash_type, }) @@ -168,10 +177,10 @@ impl ImageHash { /// The length of a row in a 2D matrix when packed into a 1D array. pub type Rowstride = usize; -/// A 2-dimensional Discrete Cosine Transform function that receives +/// A 2-dimensional Discrete Cosine Transform function that receives /// and returns 1-dimensional packed data. /// -/// The function will be provided the pre-hash data as a 1D-packed vector, +/// The function will be provided the pre-hash data as a 1D-packed vector, /// which should be interpreted as a 2D matrix with a given rowstride: /// /// ```notest @@ -193,8 +202,8 @@ impl DCT2DFunc { } fn call(&self, data: &[f64], rowstride: Rowstride) -> Vec { - (self.0)(data, rowstride) - } + (self.0)(data, rowstride) + } } impl Clone for DCT2DFunc { @@ -221,14 +230,17 @@ impl fmt::Debug for DCT2DFunc { impl hash::Hash for DCT2DFunc { /// Adds the contained function pointer as `usize`. - fn hash(&self, state: &mut H) where H: hash::Hasher { - (self.as_ptr() as usize).hash(state) + fn hash(&self, state: &mut H) + where + H: hash::Hasher, + { + (self.as_ptr() as usize).hash(state) } } /// An enum describing the hash algorithms that `img_hash` offers. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub enum HashType { +pub enum HashType { /// This algorithm first averages the pixels of the reduced-size and color image, /// and then compares each pixel to the average. /// @@ -244,9 +256,9 @@ pub enum HashType { /// /// More accurate than `Mean` but much faster than `DCT`. Gradient, - /// A version of `Gradient` that adds an extra hash pass orthogonal to the first + /// A version of `Gradient` that adds an extra hash pass orthogonal to the first /// (i.e. on columns in addition to rows). - /// + /// /// Slower than `Gradient` and produces a double-sized hash, but much more accurate. DoubleGradient, /// This algorithm runs a Discrete Cosine Transform on the reduced-color and size image, @@ -257,7 +269,7 @@ pub enum HashType { /// Call `precompute_dct_matrix()` with your chosen hash size to memoize the DCT matrix for the /// given size, which can produce significant speedups in repeated hash runs. DCT, - /// Equivalent to `DCT`, but allows the user to provide their own 2-dimensional DCT function. + /// Equivalent to `DCT`, but allows the user to provide their own 2-dimensional DCT function. /// See the `DCT2DFunc` docs for more info. /// /// Use this variant if you want a specialized or optimized 2D DCT implementation, such as from @@ -272,7 +284,7 @@ pub enum HashType { impl HashType { fn hash(self, img: &I, hash_size: u32) -> BitVec { - use HashType::*; + use HashType::*; match self { Mean => mean_hash(img, hash_size), @@ -317,8 +329,7 @@ impl HashType { fn mean_hash(img: &I, hash_size: u32) -> BitVec { let hash_values = prepare_image(img, hash_size, hash_size); - let mean = hash_values.iter().fold(0u32, |b, &a| a as u32 + b) - / hash_values.len() as u32; + let mean = hash_values.iter().fold(0u32, |b, &a| a as u32 + b) / hash_values.len() as u32; hash_values.into_iter().map(|x| x as u32 >= mean).collect() } @@ -328,10 +339,12 @@ const DCT_HASH_SIZE_MULTIPLIER: u32 = 4; fn dct_hash(img: &I, hash_size: u32, dct_2d_func: DCT2DFunc) -> BitVec { let large_size = (hash_size * DCT_HASH_SIZE_MULTIPLIER) as usize; - // We take a bigger resize than fast_hash, + // We take a bigger resize than fast_hash, // then we only take the lowest corner of the DCT let hash_values: Vec<_> = prepare_image(img, large_size as u32, large_size as u32) - .into_iter().map(|val| (val as f64) / 255.0).collect(); + .into_iter() + .map(|val| (val as f64) / 255.0) + .collect(); let dct = dct_2d_func.call(&hash_values, large_size); @@ -340,8 +353,7 @@ fn dct_hash(img: &I, hash_size: u32, dct_2d_func: DCT2DFunc) -> Bi let cropped_dct = crop_2d_dct(&dct, original, new); - let mean = cropped_dct.iter().fold(0f64, |b, &a| a + b) - / cropped_dct.len() as f64; + let mean = cropped_dct.iter().fold(0f64, |b, &a| a + b) / cropped_dct.len() as f64; cropped_dct.into_iter().map(|x| x >= mean).collect() } @@ -389,16 +401,20 @@ impl<'a, T: 'a> ops::Index for Column<'a, T> { type Output = T; #[inline(always)] fn index(&self, idx: usize) -> &T { - &self.data[idx * self.rowstride] + &self.data[idx * self.rowstride] } } -/// The guts of the gradient hash, +/// The guts of the gradient hash, /// separated so we can reuse them for both `Gradient` and `DoubleGradient`. -fn gradient_hash_impl + ?Sized>(bytes: &I, len: u32, bitv: &mut BitVec) { +fn gradient_hash_impl + ?Sized>( + bytes: &I, + len: u32, + bitv: &mut BitVec, +) { let len = len as usize; - for i in 1 .. len { + for i in 1..len { let this = &bytes[i]; let last = &bytes[i - 1]; @@ -412,7 +428,7 @@ fn gradient_hash(img: &I, hash_size: u32) -> BitVec { let mut bitv = BitVec::with_capacity((hash_size * hash_size) as usize); for row in bytes.chunks((hash_size + 1) as usize) { - gradient_hash_impl(row, hash_size, &mut bitv); + gradient_hash_impl(row, hash_size, &mut bitv); } bitv @@ -424,8 +440,7 @@ fn double_gradient_hash(img: &I, hash_size: u32) -> BitVec { let bytes = prepare_image(img, rowstride, rowstride); let mut bitv = BitVec::with_capacity((hash_size * hash_size * 2) as usize); - - for row in bytes.chunks(rowstride as usize) { + for row in bytes.chunks(rowstride as usize) { gradient_hash_impl(row, rowstride, &mut bitv); } @@ -466,7 +481,9 @@ pub trait HashImage { /// Call `iter_fn` for each pixel in the image, passing `(x, y, [pixel data])`. /// /// The iteration order is unspecified. Implementations should use whatever is optimum. - fn foreach_pixel(&self, iter_fn: F) where F: FnMut(u32, u32, &[u8]); + fn foreach_pixel(&self, iter_fn: F) + where + F: FnMut(u32, u32, &[u8]); } fn prepare_image(img: &I, width: u32, height: u32) -> Vec { @@ -483,12 +500,14 @@ fn crop_2d_dct(packed: &[f64], original: (usize, usize), new: (usize, usize)) -> assert!(new_width < orig_width && new_height < orig_height); - (0 .. new_height).flat_map(|y| { - let start = y * orig_width; - let end = start + new_width; + (0..new_height) + .flat_map(|y| { + let start = y * orig_width; + let end = start + new_width; - packed[start .. end].iter().cloned() - }).collect() + packed[start..end].iter().cloned() + }) + .collect() } #[cfg(test)] @@ -497,7 +516,7 @@ mod test { use serialize::base64::*; - use image::{Rgba, ImageBuffer}; + use image::{ImageBuffer, Rgba}; use self::rand::{weak_rng, Rng}; @@ -508,8 +527,10 @@ mod test { fn gen_test_img(width: u32, height: u32) -> RgbaBuf { let len = (width * height * 4) as usize; let mut buf = Vec::with_capacity(len); - unsafe { buf.set_len(len); } // We immediately fill the buffer. - weak_rng().fill_bytes(&mut *buf); + unsafe { + buf.set_len(len); + } // We immediately fill the buffer. + ImageBuffer::from_raw(width, height, buf).unwrap() } @@ -539,14 +560,14 @@ mod test { macro_rules! test_hash_type { ($type:ident, $modname:ident) => { mod $modname { - use {HashType, ImageHash}; use super::*; + use {HashType, ImageHash}; test_hash_equality!(hash_eq_8, 8, $type); test_hash_equality!(hash_eq_16, 16, $type); test_hash_equality!(hash_eq_32, 32, $type); } - } + }; } test_hash_type!(Mean, mean); @@ -557,7 +578,7 @@ mod test { #[test] fn dct_2d_equality() { - fn dummy_dct(_ : &[f64], _: usize) -> Vec { + fn dummy_dct(_: &[f64], _: usize) -> Vec { unimplemented!(); } @@ -569,11 +590,11 @@ mod test { #[test] fn dct_2d_inequality() { - fn dummy_dct(_ : &[f64], _: usize) -> Vec { + fn dummy_dct(_: &[f64], _: usize) -> Vec { unimplemented!(); } - fn dummy_dct_2(_ : &[f64], _: usize) -> Vec { + fn dummy_dct_2(_: &[f64], _: usize) -> Vec { unimplemented!(); } @@ -587,7 +608,7 @@ mod test { fn size() { let test_img = gen_test_img(1024, 1024); let hash = ImageHash::hash(&test_img, 32, HashType::Mean); - assert_eq!(32*32, hash.size()); + assert_eq!(32 * 32, hash.size()); } #[test] @@ -599,14 +620,14 @@ mod test { let decoded_result = ImageHash::from_base64(&*base64_string); assert_eq!(decoded_result.unwrap(), hash1); - } + } #[test] fn base64_error_on_empty() { let decoded_result = ImageHash::from_base64(""); match decoded_result { Err(InvalidBase64Length) => (), - _ => panic!("Expected a invalid length error") + _ => panic!("Expected a invalid length error"), }; } @@ -617,26 +638,24 @@ mod test { extern crate test; - use ::{HashType, ImageHash}; - use self::test::Bencher; + use {HashType, ImageHash}; const BENCH_HASH_SIZE: u32 = 8; const TEST_IMAGE_SIZE: u32 = 64; fn bench_hash(b: &mut Bencher, hash_type: HashType) { let test_img = gen_test_img(TEST_IMAGE_SIZE, TEST_IMAGE_SIZE); - b.iter(|| ImageHash::hash(&test_img, BENCH_HASH_SIZE, hash_type)); } macro_rules! bench_hash { - ($bench_fn:ident : $hash_type:expr) => ( + ($bench_fn:ident : $hash_type:expr) => { #[bench] fn $bench_fn(b: &mut Bencher) { bench_hash(b, $hash_type); } - ) + }; } bench_hash! { bench_mean_hash : HashType::Mean } @@ -644,7 +663,6 @@ mod test { bench_hash! { bench_dbl_gradient_hash : HashType::DoubleGradient } bench_hash! { bench_block_hash: HashType::Block } - #[bench] fn bench_dct_hash(b: &mut Bencher) { ::dct::clear_precomputed_matrix(); @@ -664,13 +682,12 @@ mod test { fill_rand(&mut test_vals); - let mut output = [0f64; ROW_LEN]; + let mut output = [0f64; ROW_LEN]; ::dct::clear_precomputed_matrix(); // Explicit slicing is necessary b.iter(|| ::dct::dct_1d(&test_vals[..], &mut output[..], ROW_LEN)); - test::black_box(&output); } @@ -681,7 +698,7 @@ mod test { fill_rand(&mut test_vals); - let mut output = [0f64; ROW_LEN]; + let mut output = [0f64; ROW_LEN]; ::dct::precomp_exact(ROW_LEN as u32); From 1c64de6397a8bff97ecc527752d8ddee3eb00793 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Mon, 9 Sep 2019 15:01:44 +0200 Subject: [PATCH 2/7] Upgrade to image 0.22+ --- Cargo.toml | 5 ++--- src/rust_image.rs | 46 +++++++++++++++++++++++----------------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b683d01..dd5a303 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,9 @@ bit-vec = "0.4" rustc-serialize = "0.3" [dev-dependencies] -rand = "0.3" -image = ">=0.10, <=0.19" +image = "0.22.2" [dependencies.image] -version = ">=0.10, <=0.19" +version = "0.22.2" optional = true diff --git a/src/rust_image.rs b/src/rust_image.rs index 204fc77..ea81eda 100644 --- a/src/rust_image.rs +++ b/src/rust_image.rs @@ -13,7 +13,7 @@ use image::{ GrayAlphaImage, RgbImage, RgbaImage, - GenericImage, + GenericImageView, Pixel }; @@ -43,7 +43,7 @@ macro_rules! hash_img_impl { } fn channel_count() -> u8 { - <::Pixel as Pixel>::channel_count() + <::Pixel as Pixel>::CHANNEL_COUNT } fn foreach_pixel(&self, mut iter_fn: F) where F: FnMut(u32, u32, &[u8]) { @@ -62,31 +62,31 @@ hash_img_impl! { } impl HashImage for DynamicImage { - type Grayscale = GrayImage; + type Grayscale = GrayImage; - fn dimensions(&self) -> (u32, u32) { - ::dimensions(self) - } + fn dimensions(&self) -> (u32, u32) { + ::dimensions(self) + } - fn resize(&self, width: u32, height: u32) -> Self { - self.resize(width, height, FILTER_TYPE) - } + fn resize(&self, width: u32, height: u32) -> Self { + self.resize(width, height, FILTER_TYPE) + } - fn grayscale(&self) -> GrayImage { - imageops::grayscale(self) - } + fn grayscale(&self) -> GrayImage { + imageops::grayscale(self) + } - fn to_bytes(self) -> Vec { - self.raw_pixels() - } + fn to_bytes(self) -> Vec { + self.raw_pixels() + } - fn channel_count() -> u8 { - <::Pixel as Pixel>::channel_count() - } + fn channel_count() -> u8 { + <::Pixel as Pixel>::CHANNEL_COUNT + } - fn foreach_pixel(&self, mut iter_fn: F) where F: FnMut(u32, u32, &[u8]) { - for (x, y, px) in self.pixels() { - iter_fn(x, y, px.channels()); - } - } + fn foreach_pixel(&self, mut iter_fn: F) where F: FnMut(u32, u32, &[u8]) { + for (x, y, px) in self.pixels() { + iter_fn(x, y, px.channels()); } + } +} From 15f7f10e6d80349af4b61fbcefe8fcca4ed9b002 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Mon, 9 Sep 2019 15:02:05 +0200 Subject: [PATCH 3/7] Upgrade to rand 0.7.0 --- Cargo.toml | 1 + src/lib.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dd5a303..f8cbe1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ bit-vec = "0.4" rustc-serialize = "0.3" [dev-dependencies] +rand = { version = "0.7", features = ["small_rng"] } image = "0.22.2" [dependencies.image] diff --git a/src/lib.rs b/src/lib.rs index eef4146..1d5f6f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -518,7 +518,7 @@ mod test { use image::{ImageBuffer, Rgba}; - use self::rand::{weak_rng, Rng}; + use self::rand::{rngs::SmallRng, RngCore, SeedableRng}; use super::{DCT2DFunc, HashType, ImageHash}; @@ -531,6 +531,7 @@ mod test { buf.set_len(len); } // We immediately fill the buffer. + SmallRng::from_entropy().fill_bytes(&mut *buf); ImageBuffer::from_raw(width, height, buf).unwrap() } From 9817cfd4053810eed184f2abd124b6b4ae0736c5 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Mon, 9 Sep 2019 15:02:17 +0200 Subject: [PATCH 4/7] Bump bit-vec --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f8cbe1a..063e254 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ rust-image = ["image"] bench = [] [dependencies] -bit-vec = "0.4" +bit-vec = "0.6" rustc-serialize = "0.3" [dev-dependencies] From 27075899e96e221c5f3515e388fb5706a3bee31f Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Tue, 10 Sep 2019 09:16:31 +0200 Subject: [PATCH 5/7] Fix to/from_base64 --- src/lib.rs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1d5f6f3..3ed7384 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,11 +154,17 @@ impl ImageHash { if data.len() == 0 { return Err(FromBase64Error::InvalidBase64Length); } - let hash_type = HashType::from_byte(data.remove(0)); + + let nfo = data.remove(0); + let mut bitv = BitVec::from_bytes(&*data); + + // Remove the padded bytes from the bitvec so that the number + // of bits matches the original hash that we are decoding + bitv.truncate(bitv.len() - (nfo >> 5) as usize); Ok(ImageHash { - bitv: BitVec::from_bytes(&*data), - hash_type: hash_type, + bitv, + hash_type: HashType::from_byte(nfo & 0x1F), }) } @@ -167,8 +173,21 @@ impl ImageHash { /// Mostly for printing convenience. pub fn to_base64(&self) -> String { let mut bytes = self.bitv.to_bytes(); + + // bit-vec automatically pads any trailing bits into a full + // byte with zeros, which throws off hash comparisons if + // using one loaded from the base64 representation because + // it looks like it has more bits than were in the original + // hash + let padding = (bytes.len() * 8 - self.bitv.len()) as u8; + + // We only need a max of 3 bits to store the pad length, so + // just stuff it into the top 3 bits and leave the bottom + // bits for the hash_type + let nfo = self.hash_type.to_byte() | (padding << 5); + // Insert the hash type as the first byte. - bytes.insert(0, self.hash_type.to_byte()); + bytes.insert(0, nfo); bytes.to_base64(STANDARD) } @@ -615,7 +634,11 @@ mod test { #[test] fn base64_encoding_decoding() { let test_img = gen_test_img(1024, 1024); - let hash1 = ImageHash::hash(&test_img, 32, HashType::Mean); + + // Use doublegradient for a longer hash that will have a few extra bits at the end + // to ensure we can encode/decode bitvecs that are not exactly divisible by 8, to + // handle how bitvec pads during to_bytes() + let hash1 = ImageHash::hash(&test_img, 32, HashType::DoubleGradient); let base64_string = hash1.to_base64(); let decoded_result = ImageHash::from_base64(&*base64_string); From 7c2bf5ebf7baddeb5f15da570bf99e91a12f9265 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Tue, 10 Sep 2019 09:28:08 +0200 Subject: [PATCH 6/7] Fix clippy lints --- src/block.rs | 32 ++++++++++++++++++++++++-------- src/dct.rs | 6 +++--- src/lib.rs | 23 +++++++++++++---------- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/block.rs b/src/block.rs index 66d2c1a..b32bdde 100644 --- a/src/block.rs +++ b/src/block.rs @@ -65,14 +65,17 @@ fn blockhash_slow(img: &I, size: u32) -> BitVec { let (width, height) = img.dimensions(); // Block dimensions, in pixels - let (block_width, block_height) = (width as f64 / size as f64, height as f64 / size as f64); + let (block_width, block_height) = { + let size = f64::from(size); + (f64::from(width) / size, f64::from(height) / size) + }; let idx = |x, y| (y * size + x) as usize; img.foreach_pixel(|x, y, px| { - let px_sum = sum_px(px) as f64; + let px_sum = f64::from(sum_px(px)); - let (x, y) = (x as f64, y as f64); + let (x, y) = (f64::from(x), f64::from(y)); let block_x = x / block_width; let block_y = y / block_height; @@ -138,10 +141,22 @@ fn blockhash_fast(img: &I, size: u32) -> BitVec { fn sum_px(px: &[u8]) -> u32 { // Branch prediction should eliminate the match after a few iterations match px.len() { - 4 => if px[3] == 0 { 255 * 3 } else { sum_px(&px[..3]) }, - 3 => px[0] as u32 + px[1] as u32 + px[2] as u32, - 2 => if px[1] == 0 { 255 } else { px[0] as u32 }, - 1 => px[0] as u32, + 4 => { + if px[3] == 0 { + 255 * 3 + } else { + sum_px(&px[..3]) + } + } + 3 => u32::from(px[0]) + u32::from(px[1]) + u32::from(px[2]), + 2 => { + if px[1] == 0 { + 255 + } else { + u32::from(px[0]) + } + } + 1 => u32::from(px[0]), // We can only hit this assertion if there's a bug where the number of values // per pixel doesn't match HashImage::channel_count _ => panic!("Channel count was different than actual pixel size"), @@ -149,8 +164,9 @@ fn sum_px(px: &[u8]) -> u32 { } // Get the next multiple of 4 up from x, or x if x is a multiple of 4 +#[inline] fn next_multiple_of_4(x: u32) -> u32 { - x + 3 & !3 + (x + 3) & !3 } fn get_median(data: &[T]) -> T { diff --git a/src/dct.rs b/src/dct.rs index 7a11ad6..d6049e8 100644 --- a/src/dct.rs +++ b/src/dct.rs @@ -21,8 +21,8 @@ impl<'a, T: 'a> ColumnsMut<'a, T> { #[inline(always)] fn from_slice(data: &'a mut [T], rowstride: usize) -> Self { ColumnsMut { - data: data, - rowstride: rowstride, + data, + rowstride, curr: 0, } } @@ -36,7 +36,7 @@ impl<'a, T: 'a> Iterator for ColumnsMut<'a, T> { let data = unsafe { &mut *(&mut self.data[self.curr..] as *mut [T]) }; self.curr += 1; Some(ColumnMut { - data: data, + data, rowstride: self.rowstride, }) } else { diff --git a/src/lib.rs b/src/lib.rs index 3ed7384..2072f3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,7 +86,7 @@ impl ImageHash { ImageHash { bitv: hash, - hash_type: hash_type, + hash_type, } } @@ -151,7 +151,7 @@ impl ImageHash { pub fn from_base64(encoded_hash: &str) -> Result { let mut data = try!(encoded_hash.from_base64()); // The hash type should be the first bit of the hash - if data.len() == 0 { + if data.is_empty() { return Err(FromBase64Error::InvalidBase64Length); } @@ -216,11 +216,11 @@ pub type Rowstride = usize; pub struct DCT2DFunc(pub fn(&[f64], Rowstride) -> Vec); impl DCT2DFunc { - fn as_ptr(&self) -> *const () { + fn as_ptr(self) -> *const () { self.0 as *const () } - fn call(&self, data: &[f64], rowstride: Rowstride) -> Vec { + fn call(self, data: &[f64], rowstride: Rowstride) -> Vec { (self.0)(data, rowstride) } } @@ -348,9 +348,12 @@ impl HashType { fn mean_hash(img: &I, hash_size: u32) -> BitVec { let hash_values = prepare_image(img, hash_size, hash_size); - let mean = hash_values.iter().fold(0u32, |b, &a| a as u32 + b) / hash_values.len() as u32; + let mean = hash_values.iter().fold(0u32, |b, &a| u32::from(a) + b) / hash_values.len() as u32; - hash_values.into_iter().map(|x| x as u32 >= mean).collect() + hash_values + .into_iter() + .map(|x| u32::from(x) >= mean) + .collect() } const DCT_HASH_SIZE_MULTIPLIER: u32 = 4; @@ -362,7 +365,7 @@ fn dct_hash(img: &I, hash_size: u32, dct_2d_func: DCT2DFunc) -> Bi // then we only take the lowest corner of the DCT let hash_values: Vec<_> = prepare_image(img, large_size as u32, large_size as u32) .into_iter() - .map(|val| (val as f64) / 255.0) + .map(|val| f64::from(val) / 255.0) .collect(); let dct = dct_2d_func.call(&hash_values, large_size); @@ -387,8 +390,8 @@ impl<'a, T: 'a> Columns<'a, T> { #[inline(always)] fn from_slice(data: &'a [T], rowstride: usize) -> Self { Columns { - data: data, - rowstride: rowstride, + data, + rowstride, curr: 0, } } @@ -402,7 +405,7 @@ impl<'a, T: 'a> Iterator for Columns<'a, T> { let data = &self.data[self.curr..]; self.curr += 1; Some(Column { - data: data, + data, rowstride: self.rowstride, }) } else { From c40da78946840c810543e6ec53967afb7adafd74 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Tue, 10 Sep 2019 09:28:15 +0200 Subject: [PATCH 7/7] Rustfmt --- src/block.rs | 61 ++++++++++++++++++++++++++----------------- src/dct.rs | 66 ++++++++++++++++++++++++++--------------------- src/rust_image.rs | 24 +++++++---------- 3 files changed, 85 insertions(+), 66 deletions(-) diff --git a/src/block.rs b/src/block.rs index b32bdde..7b88ec8 100644 --- a/src/block.rs +++ b/src/block.rs @@ -20,18 +20,18 @@ const FLOAT_EQ_MARGIN: f64 = 0.001; pub fn blockhash(img: &I, size: u32) -> BitVec { let size = next_multiple_of_4(size); - let (width, height) = img.dimensions(); + let (width, height) = img.dimensions(); // Skip the floating point math if it's unnecessary if width % size == 0 && height % size == 0 { blockhash_fast(img, size) } else { blockhash_slow(img, size) - } -} + } +} macro_rules! gen_hash { - ($imgty:ty, $valty:ty, $blocks: expr, $size:expr, $block_width:expr, $block_height:expr, $eq_fn:expr) => ({ + ($imgty:ty, $valty:ty, $blocks: expr, $size:expr, $block_width:expr, $block_height:expr, $eq_fn:expr) => {{ let channel_count = <$imgty as HashImage>::channel_count() as u32; let group_len = (($size * $size) / 4) as usize; @@ -41,29 +41,32 @@ macro_rules! gen_hash { let cmp_factor = match channel_count { 3 | 4 => 255u32 as $valty * 3u32 as $valty, 2 | 1 => 255u32 as $valty, - _ => panic!("Unrecognized channel count from HashImage: {}", channel_count), - } - * block_area + _ => panic!( + "Unrecognized channel count from HashImage: {}", + channel_count + ), + } * block_area / (2u32 as $valty); let medians: Vec<$valty> = $blocks.chunks(group_len).map(get_median).collect(); - $blocks.chunks(group_len).zip(medians) - .flat_map(|(blocks, median)| - blocks.iter().map(move |&block| - block > median || - ($eq_fn(block, median) && median > cmp_factor) - ) - ) + $blocks + .chunks(group_len) + .zip(medians) + .flat_map(|(blocks, median)| { + blocks.iter().map(move |&block| { + block > median || ($eq_fn(block, median) && median > cmp_factor) + }) + }) .collect() - }) + }}; } fn blockhash_slow(img: &I, size: u32) -> BitVec { let mut blocks = vec![0f64; (size * size) as usize]; let (width, height) = img.dimensions(); - + // Block dimensions, in pixels let (block_width, block_height) = { let size = f64::from(size); @@ -112,9 +115,15 @@ fn blockhash_slow(img: &I, size: u32) -> BitVec { blocks[idx(block_right, block_bottom)] += px_sum * weight_right * weight_bottom; }); - - gen_hash!(I, f64, blocks, size, block_width, block_height, - |l: f64, r: f64| (l - r).abs() < FLOAT_EQ_MARGIN) + gen_hash!( + I, + f64, + blocks, + size, + block_width, + block_height, + |l: f64, r: f64| (l - r).abs() < FLOAT_EQ_MARGIN + ) } fn blockhash_fast(img: &I, size: u32) -> BitVec { @@ -125,7 +134,7 @@ fn blockhash_fast(img: &I, size: u32) -> BitVec { let idx = |x, y| (y * size + x) as usize; - img.foreach_pixel(|x, y, px| { + img.foreach_pixel(|x, y, px| { let px_sum = sum_px(px); let block_x = x / block_width; @@ -134,7 +143,8 @@ fn blockhash_fast(img: &I, size: u32) -> BitVec { blocks[idx(block_x, block_y)] += px_sum; }); - gen_hash!(I, u32, blocks, size, block_width, block_height, |l, r| l == r) + gen_hash!(I, u32, blocks, size, block_width, block_height, |l, r| l + == r) } #[inline(always)] @@ -180,7 +190,12 @@ const SORT_THRESH: usize = 8; fn qselect_inplace(data: &mut [T], k: usize) -> &mut T { let len = data.len(); - assert!(k < len, "Called qselect_inplace with k = {} and data length: {}", k, len); + assert!( + k < len, + "Called qselect_inplace with k = {} and data length: {}", + k, + len + ); if len < SORT_THRESH { data.sort_by(|left, right| left.partial_cmp(right).unwrap_or(Ordering::Less)); @@ -213,7 +228,7 @@ fn partition(data: &mut [T]) -> usize { let mut curr = 0; - for i in 0 .. len - 1 { + for i in 0..len - 1 { if &data[i] < &data[len - 1] { data.swap(i, curr); curr += 1; diff --git a/src/dct.rs b/src/dct.rs index d6049e8..e8cb4f5 100644 --- a/src/dct.rs +++ b/src/dct.rs @@ -33,7 +33,7 @@ impl<'a, T: 'a> Iterator for ColumnsMut<'a, T> { #[inline(always)] fn next(&mut self) -> Option { if self.curr < self.rowstride { - let data = unsafe { &mut *(&mut self.data[self.curr..] as *mut [T]) }; + let data = unsafe { &mut *(&mut self.data[self.curr..] as *mut [T]) }; self.curr += 1; Some(ColumnMut { data, @@ -54,14 +54,14 @@ impl<'a, T: 'a> Index for ColumnMut<'a, T> { type Output = T; #[inline(always)] fn index(&self, idx: usize) -> &T { - &self.data[idx * self.rowstride] + &self.data[idx * self.rowstride] } } impl<'a, T: 'a> IndexMut for ColumnMut<'a, T> { #[inline(always)] fn index_mut(&mut self, idx: usize) -> &mut T { - &mut self.data[idx * self.rowstride] + &mut self.data[idx * self.rowstride] } } @@ -108,15 +108,17 @@ pub fn clear_precomputed_matrix() { fn precompute_matrix(size: usize, matrix: &mut Vec) { matrix.resize(size * size, 0.0); - for i in 0 .. size { - for j in 0 .. size { + for i in 0..size { + for j in 0..size { matrix[i * size + j] = (PI * i as f64 * (2 * j + 1) as f64 / (2 * size) as f64).cos(); } } } fn with_precomputed_matrix(size: usize, with_fn: F) -> bool -where F: FnOnce(&[f64]) { +where + F: FnOnce(&[f64]), +{ PRECOMPUTED_MATRIX.with(|matrix| { let matrix = matrix.borrow(); @@ -129,35 +131,39 @@ where F: FnOnce(&[f64]) { }) } -pub fn dct_1d + ?Sized, O: IndexMut + ?Sized>(input: &I, output: &mut O, len: usize) { +pub fn dct_1d + ?Sized, O: IndexMut + ?Sized>( + input: &I, + output: &mut O, + len: usize, +) { if with_precomputed_matrix(len, |matrix| dct_1d_precomputed(input, output, len, matrix)) { return; } - for i in 0 .. len { + for i in 0..len { let mut z = 0.0; - for j in 0 .. len { - z += input[j] * ( - PI * i as f64 * (2 * j + 1) as f64 - / (2 * len) as f64 - ).cos(); - } + for j in 0..len { + z += input[j] * (PI * i as f64 * (2 * j + 1) as f64 / (2 * len) as f64).cos(); + } if i == 0 { z *= 1.0 / SQRT_2; } output[i] = z / 2.0; - } + } } fn dct_1d_precomputed(input: &I, output: &mut O, len: usize, matrix: &[f64]) -where I: Index, O: IndexMut { - for i in 0 .. len { +where + I: Index, + O: IndexMut, +{ + for i in 0..len { let mut z = 0.0; - for j in 0 .. len { + for j in 0..len { z += input[j] * matrix[i * len + j]; } @@ -178,18 +184,23 @@ pub fn dct_2d(packed_2d: &[f64], rowstride: usize) -> Vec { assert_eq!(packed_2d.len() % rowstride, 0); let mut scratch = Vec::with_capacity(packed_2d.len() * 2); - unsafe { scratch.set_len(packed_2d.len() * 2); } + unsafe { + scratch.set_len(packed_2d.len() * 2); + } { let (col_pass, row_pass) = scratch.split_at_mut(packed_2d.len()); - - for (row_in, row_out) in packed_2d.chunks(rowstride) - .zip(row_pass.chunks_mut(rowstride)) { + + for (row_in, row_out) in packed_2d + .chunks(rowstride) + .zip(row_pass.chunks_mut(rowstride)) + { dct_1d(row_in, row_out, rowstride); } for (col_in, mut col_out) in Columns::from_slice(row_pass, rowstride) - .zip(ColumnsMut::from_slice(col_pass, rowstride)) { + .zip(ColumnsMut::from_slice(col_pass, rowstride)) + { dct_1d(&col_in, &mut col_out, rowstride); } } @@ -204,7 +215,6 @@ mod dct_simd { use simdty::f64x2; use std::f64::consts::{PI, SQRT_2}; - macro_rules! valx2 ( ($val:expr) => ( ::simdty::f64x2($val, $val) ) ); const PI: f64x2 = valx2!(PI); @@ -223,7 +233,6 @@ mod dct_simd { dct_1dx2(vals); - } } @@ -236,7 +245,7 @@ mod dct_simd { for x in 0 .. vec.len() { z += vec[x] * cos_approx( PI * valx2!( - u as f64 * (2 * x + 1) as f64 + u as f64 * (2 * x + 1) as f64 / (2 * vec.len()) as f64 ) ); @@ -249,7 +258,7 @@ mod dct_simd { out.insert(u, z / valx2!(2.0)); } - out + out } fn cos_approx(x2: f64x2) -> f64x2 { @@ -263,9 +272,8 @@ mod dct_simd { let x6 = powi(val, 6); let x8 = powi(val, 8); - valx2!(1.0) - (x2 / valx2!(2.0)) + (x4 / valx2!(24.0)) + valx2!(1.0) - (x2 / valx2!(2.0)) + (x4 / valx2!(24.0)) - (x6 / valx2!(720.0)) + (x8 / valx2!(40320.0)) } } */ - diff --git a/src/rust_image.rs b/src/rust_image.rs index ea81eda..2f5cdeb 100644 --- a/src/rust_image.rs +++ b/src/rust_image.rs @@ -6,15 +6,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. use image::{ - imageops, - DynamicImage, - FilterType, - GrayImage, - GrayAlphaImage, - RgbImage, - RgbaImage, - GenericImageView, - Pixel + imageops, DynamicImage, FilterType, GenericImageView, GrayAlphaImage, GrayImage, Pixel, + RgbImage, RgbaImage, }; use super::HashImage; @@ -56,16 +49,16 @@ macro_rules! hash_img_impl { ($($ty:ident ($lumaty:ty)),+) => ( $(hash_img_impl! { $ty($lumaty) })+ ); } -hash_img_impl! { - GrayImage(GrayImage), GrayAlphaImage(GrayImage), - RgbImage(GrayImage), RgbaImage(GrayImage) +hash_img_impl! { + GrayImage(GrayImage), GrayAlphaImage(GrayImage), + RgbImage(GrayImage), RgbaImage(GrayImage) } impl HashImage for DynamicImage { type Grayscale = GrayImage; fn dimensions(&self) -> (u32, u32) { - ::dimensions(self) + ::dimensions(self) } fn resize(&self, width: u32, height: u32) -> Self { @@ -84,7 +77,10 @@ impl HashImage for DynamicImage { <::Pixel as Pixel>::CHANNEL_COUNT } - fn foreach_pixel(&self, mut iter_fn: F) where F: FnMut(u32, u32, &[u8]) { + fn foreach_pixel(&self, mut iter_fn: F) + where + F: FnMut(u32, u32, &[u8]), + { for (x, y, px) in self.pixels() { iter_fn(x, y, px.channels()); }