#![crate_name = "image_compare"]
#![warn(missing_docs)]
#![warn(unused_qualifications)]
#![deny(deprecated)]
mod colorization;
mod histogram;
mod hybrid;
mod squared_error;
mod ssim;
#[cfg(not(feature="yuv_compare"))] mod utils;
#[cfg(feature="yuv_compare")] pub mod utils;
#[doc(hidden)]
pub mod prelude {
pub use image::{GrayImage, ImageBuffer, Luma, Rgb, RgbImage};
use thiserror::Error;
pub enum Algorithm {
RootMeanSquared,
MSSIMSimple,
}
#[derive(Error, Debug)]
pub enum CompareError {
#[error("The dimensions of the input images are not identical")]
DimensionsDiffer,
#[error("Comparison calculation failed: {0}")]
CalculationFailed(String),
}
pub use crate::colorization::GraySimilarityImage;
pub use crate::colorization::RGBASimilarityImage;
pub use crate::colorization::RGBSimilarityImage;
pub use crate::colorization::Similarity;
}
#[doc(inline)]
pub use histogram::Metric;
#[doc(inline)]
pub use prelude::Algorithm;
#[doc(inline)]
pub use prelude::CompareError;
#[doc(inline)]
pub use prelude::Similarity;
use prelude::*;
use utils::Decompose;
pub fn gray_similarity_structure(
algorithm: &Algorithm,
first: &GrayImage,
second: &GrayImage,
) -> Result<Similarity, CompareError> {
if first.dimensions() != second.dimensions() {
return Err(CompareError::DimensionsDiffer);
}
match algorithm {
Algorithm::RootMeanSquared => root_mean_squared_error_simple(first, second),
Algorithm::MSSIMSimple => ssim_simple(first, second),
}
.map(|(score, i)| Similarity {
image: i.into(),
score,
})
}
pub fn rgb_similarity_structure(
algorithm: &Algorithm,
first: &RgbImage,
second: &RgbImage,
) -> Result<Similarity, CompareError> {
if first.dimensions() != second.dimensions() {
return Err(CompareError::DimensionsDiffer);
}
let first_channels = first.split_channels();
let second_channels = second.split_channels();
let mut results = Vec::new();
for channel in 0..3 {
match algorithm {
Algorithm::RootMeanSquared => {
results.push(root_mean_squared_error_simple(
&first_channels[channel],
&second_channels[channel],
)?);
}
Algorithm::MSSIMSimple => {
results.push(ssim_simple(
&first_channels[channel],
&second_channels[channel],
)?);
}
}
}
let input = results.iter().map(|(_, i)| i).collect::<Vec<_>>();
let image = utils::merge_similarity_channels(&input.try_into().unwrap());
let score = results.iter().map(|(s, _)| *s).fold(1., f64::min);
Ok(Similarity {
image: image.into(),
score,
})
}
pub fn gray_similarity_histogram(
metric: Metric,
first: &GrayImage,
second: &GrayImage,
) -> Result<f64, CompareError> {
if first.dimensions() != second.dimensions() {
return Err(CompareError::DimensionsDiffer);
}
histogram::img_compare(first, second, metric)
}
#[doc(inline)]
pub use hybrid::rgb_hybrid_compare;
use crate::squared_error::root_mean_squared_error_simple;
use crate::ssim::ssim_simple;
#[doc(inline)]
pub use hybrid::rgba_hybrid_compare;
#[doc(inline)]
pub use hybrid::rgba_blended_hybrid_compare;
#[doc(inline)]
#[cfg(feature = "yuv_compare")]
pub use hybrid::yuv_hybrid_compare;
pub use hybrid::BlendInput;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dimensions_differ_test_gray_structure() {
let first = GrayImage::new(1, 1);
let second = GrayImage::new(2, 2);
let result = gray_similarity_structure(&Algorithm::RootMeanSquared, &first, &second);
assert!(result.is_err());
}
#[test]
fn dimensions_differ_test_rgb_structure() {
let first = RgbImage::new(1, 1);
let second = RgbImage::new(2, 2);
let result = rgb_similarity_structure(&Algorithm::RootMeanSquared, &first, &second);
assert!(result.is_err());
}
#[test]
fn dimensions_differ_test_gray_histos() {
let first = GrayImage::new(1, 1);
let second = GrayImage::new(2, 2);
let result = gray_similarity_histogram(Metric::Hellinger, &first, &second);
assert!(result.is_err());
}
}