1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//!  A module for comparing images.

// from rust

// from external crate

// from local crate
use error::RasterResult;
use Image;
use editor::{self, ResizeMode};

/// Compare two images and returns a hamming distance. A value of 0 indicates a likely similar
/// picture. A value between 1 and 10 is potentially a variation. A value greater than 10 is
/// likely a different image.
///
/// # Examples
/// ```
/// use raster::compare;
///
/// let image1 = raster::open("tests/in/sample.jpg").unwrap();
/// let image2 = raster::open("tests/in/sample.png").unwrap();
///
/// let hamming_distance = compare::similar(&image1, &image2).unwrap();
/// println!("{}", hamming_distance);
/// ```
pub fn similar(image1: &Image, image2: &Image) -> RasterResult<u8> {
    let bin1 = diff_hash(image1)?;
    let bin2 = diff_hash(image2)?;
    let mut distance = 0;
    for (index, value) in bin1.iter().enumerate() {
        if value != &bin2[index] {
            distance += 1;
        }
    }
    Ok(distance)
}

/// Compare if two images are equal. It will compare if the two images are of the same width and
/// height. If the dimensions differ, it will return false. If the dimensions are equal, it will
/// loop through each pixels. If one of the pixel don't match, it will return false. The pixels
/// are compared using their RGB (Red, Green, Blue) values.
///
/// # Examples
/// ```
/// use raster::compare;
///
/// let image1 = raster::open("tests/in/sample.png").unwrap();
/// let image2 = raster::open("tests/in/sample.png").unwrap();
///
/// let equal = compare::equal(&image1, &image2).unwrap();
/// assert_eq!(true, equal);
/// ```
pub fn equal(image1: &Image, image2: &Image) -> RasterResult<bool> {
    // Check if image dimensions are equal
    if image1.width != image2.width || image1.height != image2.height {
        Ok(false)
    } else {
        // Loop using image1
        for y in 0..image1.height {
            for x in 0..image1.width {
                // Get image1 pixel
                let pixel1 = image1.get_pixel(x, y)?;

                // Get image2 pixel
                let pixel2 = image2.get_pixel(x, y)?;

                // Compare pixel value
                if pixel1.r != pixel2.r || pixel1.g != pixel2.g || pixel1.b != pixel2.b {
                    return Ok(false);
                }
            }
        }

        Ok(true)
    }
}

// Private functions

// DifferenceHash
//
// Algorithm:
// Reduce size. The fastest way to remove high frequencies and detail is to shrink the image. In
// this case, shrink it to 9x8 so that there are 72 total pixels. Reduce color. Convert the image
// to a grayscale picture. This changes the hash from 72 pixels to a total of 72 colors. Compute
// the difference. The algorithm works on the difference between adjacent pixels. This identifies
// the relative gradient direction. In this case, the 9 pixels per row yields 8 differences
// between adjacent pixels. Eight rows of eight differences becomes 64 bits. Assign bits. Each bit
// is simply set based on whether the left pixel is brighter than the right pixel.
//
// http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
//
//
fn diff_hash(image: &Image) -> RasterResult<Vec<u8>> {
    let width = 9;
    let height = 8;

    let mut image = image.clone(); // copy it since resize is desctructive
    editor::resize(&mut image, width, height, ResizeMode::Exact)?; // Resize to exactly 9x8

    // Build hash
    let mut hash = Vec::new();
    for y in 0..height {
        // Get the pixel value for the leftmost pixel.
        let pixel = image.get_pixel(0, y)?;
        let mut left = ((pixel.r as f32 + pixel.g as f32 + pixel.b as f32) / 3.0).floor();

        // Get the pixel value for each pixel starting from position 1.
        for x in 1..width {
            let pixel = image.get_pixel(x, y)?;
            let right = ((pixel.r as f32 + pixel.g as f32 + pixel.b as f32) / 3.0).floor();
            // Each hash bit is set based on whether the left pixel is brighter than the right
            // pixel.
            if left > right {
                hash.push(1);
            } else {
                hash.push(0);
            }
            // Prepare the next loop.
            left = right;
        }
    }
    Ok(hash)
}