Skip to content

Commit

Permalink
Merge pull request #4 from michaelr0/develop
Browse files Browse the repository at this point in the history
Refactor into library?
  • Loading branch information
michaelr0 authored Dec 18, 2021
2 parents a1b5049 + cc5a75e commit fe07165
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 107 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "imeq"
description = "imeq aims to quickly compare two images to see if they are the same image"
version = "0.0.1"
version = "0.0.2"
edition = "2021"
categories = ["multimedia::images"]
exclude = ["images/*"]
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,26 @@ The below benchmarks have been done on a 2020 M1 Mac Mini with 16gb of ram and 2

### hyperfine -w 3 'target/release/imeq images/baseline.jpeg images/baseline.jpeg'
```bash
Time (mean ± σ): 1.0 ms ± 0.3 ms [User: 0.6 ms, System: 0.2 ms]
Range (min … max): 0.6 ms … 4.0 ms 936 runs
Time (mean ± σ): 1.1 ms ± 0.3 ms [User: 0.6 ms, System: 0.3 ms]
Range (min … max): 0.8 ms … 2.6 ms 857 runs
```

### hyperfine -w 3 'target/release/imeq images/baseline.jpeg images/baseline_by_another_name.jpeg'
```bash
Time (mean ± σ): 5.3 ms ± 0.4 ms [User: 7.3 ms, System: 2.2 ms]
Range (min … max): 4.8 ms … 8.3 ms 384 runs
Time (mean ± σ): 5.5 ms ± 0.4 ms [User: 7.4 ms, System: 2.3 ms]
Range (min … max): 5.1 ms … 7.5 ms 308 runs
```

### hyperfine -w 3 'target/release/imeq images/baseline.jpeg images/flipped.jpeg'
```bash
Time (mean ± σ): 90.5 ms ± 1.1 ms [User: 448.1 ms, System: 52.0 ms]
Range (min … max): 89.1 ms … 94.0 ms 31 runs
Time (mean ± σ): 93.7 ms ± 3.4 ms [User: 443.6 ms, System: 54.2 ms]
Range (min … max): 90.4 ms … 102.8 ms 31 runs
```

### hyperfine -w 3 'target/release/imeq images/baseline.jpeg images/modified.jpeg'
```bash
Time (mean ± σ): 112.5 ms ± 2.1 ms [User: 467.5 ms, System: 52.2 ms]
Range (min … max): 110.4 ms … 121.6 ms 26 runs
Time (mean ± σ): 113.1 ms ± 0.9 ms [User: 471.1 ms, System: 53.4 ms]
Range (min … max): 111.4 ms … 115.5 ms 26 runs
```

## Credits
Expand Down
175 changes: 100 additions & 75 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,94 +1,119 @@
use clap::ArgMatches;
use image::{DynamicImage, GenericImageView, Pixels};
use rayon::prelude::*;
use ring::digest::{self};
use std::{fs::File, io::Read, process::exit};

/// Check if images use the same path
pub fn get_image_names(cli: &ArgMatches) -> Vec<String> {
vec![
cli.value_of("IMAGE_1")
.expect("first image is required")
.to_string(),
cli.value_of("IMAGE_2")
.expect("second image is required")
.to_string(),
]
}
use std::{collections::HashMap, fs::File, io::Read};

/// Open images and read bytes
pub fn open_images(images: Vec<String>) -> Vec<Vec<u8>> {
images
.par_iter()
.map(|i| {
let mut buf = Vec::new();

File::open(i)
.expect("Unable to open file")
.read_to_end(&mut buf)
.unwrap();

buf
})
.collect::<Vec<Vec<u8>>>()
pub struct Compare<'a> {
images: [String; 2],
checks: HashMap<&'a str, bool>,
}

/// Generate hashes
pub fn get_hashes(images: &Vec<Vec<u8>>) -> Vec<Vec<u8>> {
images
.par_iter()
.map(|i| digest::digest(&digest::SHA512, i).as_ref().to_owned())
.collect::<Vec<Vec<u8>>>()
}
impl Compare<'_> {
pub fn new(image_1: String, image_2: String) -> Self {
let mut compare = Compare {
images: [image_1, image_2],
checks: HashMap::new(),
};

/// Check if images use the same path
pub fn check_image_same_path(images: &Vec<String>) {
if images.first() == images.last() {
println!("Images are the same file");
exit(0);
compare.checks.insert("images_have_same_path", false);
compare.checks.insert("image_hashes_match", false);
compare.checks.insert("image_dimensions_match", false);
compare.checks.insert("image_pixels_match", false);

compare
}
}

/// Check if images have matching hashes
pub fn check_image_hashes(hashes: Vec<Vec<u8>>) {
if hashes.first().eq(&hashes.last()) {
println!("Images are the same file");
exit(0);
pub fn enable_check(&mut self, check: &'static str) -> &mut Self {
self.checks.insert(check, true);

self
}
}

/// Compare images dimensions and pixels
pub fn compare_image_as_images(images: &Vec<Vec<u8>>) {
let images = images
.par_iter()
.map(|i| image::load_from_memory(i).expect("Could not read image"))
.collect::<Vec<DynamicImage>>();

// Compare images dimensions
if !image_dimensions_match(&images) {
println!("Images aren't the same dimensions");
exit(0);
pub fn enable_check_images_have_same_path(&mut self) -> &mut Self {
self.enable_check("images_have_same_path")
}

let images = images
.par_iter()
.map(|i| i.pixels())
.collect::<Vec<Pixels<DynamicImage>>>();
pub fn enable_check_image_hashes_match(&mut self) -> &mut Self {
self.enable_check("image_hashes_match")
}

let image1 = images.first().unwrap().to_owned();
let image2 = images.last().unwrap().to_owned();
pub fn enable_check_images_dimensions_match(&mut self) -> &mut Self {
self.enable_check("image_dimensions_match")
}

// Compare images pixels
if image1.ne(image2) {
println!("Images are not a match");
exit(0);
pub fn enable_check_images_pixels_match(&mut self) -> &mut Self {
self.enable_check("image_pixels_match")
}
}

/// Compare images dimensions
pub fn image_dimensions_match(images: &Vec<DynamicImage>) -> bool {
let image1_dimensions = images.first().unwrap().dimensions();
let image2_dimensions = images.last().unwrap().dimensions();
fn is_check_enabled(&self, check: &'static str) -> bool {
*self.checks.get(check).unwrap_or(&false)
}

pub fn are_match(&self) -> bool {
if self.is_check_enabled("images_have_same_path")
&& self.images.first() == self.images.last()
{
return true;
}

let images = self
.images
.par_iter()
.map(|i| {
let mut buf = Vec::new();

File::open(i)
.expect("Unable to open file")
.read_to_end(&mut buf)
.unwrap();

image1_dimensions.eq(&image2_dimensions)
buf
})
.collect::<Vec<Vec<u8>>>();

if self.is_check_enabled("image_hashes_match") {
let hashes = images
.par_iter()
.map(|i| digest::digest(&digest::SHA512, i).as_ref().to_owned())
.collect::<Vec<Vec<u8>>>();

if hashes.first().eq(&hashes.last()) {
return true;
}
}

let images = images
.par_iter()
.map(|i| image::load_from_memory(i).expect("Could not read image"))
.collect::<Vec<DynamicImage>>();

if self.is_check_enabled("image_dimensions_match") {
let image1_dimensions = images.first().unwrap().dimensions();
let image2_dimensions = images.last().unwrap().dimensions();

if image1_dimensions.ne(&image2_dimensions) {
return false;
}
}

if self.is_check_enabled("image_pixels_match") {
let images = images
.par_iter()
.map(|i| i.pixels())
.collect::<Vec<Pixels<DynamicImage>>>();

let image1 = images.first().unwrap().to_owned();
let image2 = images.last().unwrap().to_owned();

if image1.eq(image2) {
return true;
}
}

false
}

pub fn arent_match(&self) -> bool {
!self.are_match()
}
}
44 changes: 22 additions & 22 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
use clap::{load_yaml, App};

fn main() {
// Init APP/CLI
let config = load_yaml!("main.yml");
let app = App::from_yaml(config);
let cli = app.get_matches();

// Get image paths from CLI
let images = imeq::get_image_names(&cli);

// Check if images use the same path
imeq::check_image_same_path(&images);

// Open images and read bytes
let images = imeq::open_images(images);

// Generate hashes
let hashes = imeq::get_hashes(&images);

// Check if images have matching hashes
imeq::check_image_hashes(hashes);

// Compare images dimensions and pixels
imeq::compare_image_as_images(&images);

// Everything else has led to this moment!
// The images appear to match!
println!("Images are a match");
let image_1 = cli
.value_of("IMAGE_1")
.expect("first image is required")
.to_string();

let image_2 = cli
.value_of("IMAGE_2")
.expect("second image is required")
.to_string();

let images_match = imeq::Compare::new(image_1, image_2)
.enable_check_images_have_same_path()
.enable_check_image_hashes_match()
.enable_check_images_dimensions_match()
.enable_check_images_pixels_match()
.are_match();

if images_match {
println!("Images are a match");
} else {
println!("Images are not a match");
}
}
2 changes: 1 addition & 1 deletion src/main.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: imgeq
version: "0.0.1"
version: "0.0.2"
author: https://github.com/michaelr0/imeq-rs
settings:
- ArgRequiredElseHelp
Expand Down

0 comments on commit fe07165

Please sign in to comment.