1use crate::prelude::*;
2use crate::squared_error::root_mean_squared_error_simple;
3use crate::ssim::ssim_simple;
4use crate::utils::{blend_alpha, split_rgba_to_yuva};
5use crate::Decompose;
6use image::{Rgba, RgbaImage};
7use itertools::izip;
8use std::borrow::Cow;
9
10fn merge_similarity_channels_yuva(
11 input: &[GraySimilarityImage; 4],
12 alpha: &GrayImage,
13 alpha_second: &GrayImage,
14) -> Similarity {
15 const ALPHA_VIS_MIN: f32 = 0.1;
16 const U8_MAX: f32 = u8::MAX as f32;
17 const A_BAR_NORM: f32 = 2. * U8_MAX;
18
19 let mut image = RGBASimilarityImage::new(input[0].width(), input[0].height());
20 let mut deviation = Vec::new();
21 deviation.resize((input[0].width() * input[0].height()) as usize, 0.0);
22 izip!(
23 image.pixels_mut(),
24 input[0].pixels(),
25 input[1].pixels(),
26 input[2].pixels(),
27 input[3].pixels(),
28 alpha.pixels(),
29 alpha_second.pixels(),
30 deviation.iter_mut()
31 )
32 .for_each(
33 |(rgba, y, u, v, a_d, alpha_source, alpha_source_second, deviation)| {
34 let y = y[0].clamp(0.0, 1.0);
35 let u = u[0].clamp(0.0, 1.0);
36 let v = v[0].clamp(0.0, 1.0);
37 let a_d = a_d[0].clamp(0.0, 1.0);
38 let alpha_bar = (alpha_source[0] as f32 + alpha_source_second[0] as f32) / A_BAR_NORM;
39 let alpha_bar = if alpha_bar.is_finite() {
40 alpha_bar
41 } else {
42 1.0
43 };
44
45 let color_diff = ((u).powi(2) + (v).powi(2)).sqrt().clamp(0.0, 1.0);
46 let min_sim = y.min(color_diff).min(a_d);
47 let dev = if alpha_bar > 0. {
50 (min_sim / alpha_bar).clamp(0., 1.)
51 } else {
52 1.0
53 };
54 let alpha_vis = (ALPHA_VIS_MIN + a_d * (1.0 - ALPHA_VIS_MIN)).clamp(0., 1.);
55
56 *deviation = dev;
57 *rgba = Rgba([1. - y, 1. - u, 1. - v, alpha_vis]);
58 },
59 );
60
61 let score = deviation.iter().map(|s| *s as f64).sum::<f64>() / deviation.len() as f64;
62
63 Similarity {
64 image: image.into(),
65 score,
66 }
67}
68
69fn merge_similarity_channels_yuv(input: &[GraySimilarityImage; 3]) -> Similarity {
70 let mut image = RGBSimilarityImage::new(input[0].width(), input[0].height());
71 let mut deviation = Vec::new();
72 deviation.resize((input[0].width() * input[0].height()) as usize, 0.0);
73 izip!(
74 image.pixels_mut(),
75 input[0].pixels(),
76 input[1].pixels(),
77 input[2].pixels(),
78 deviation.iter_mut()
79 )
80 .for_each(|(rgb, y, u, v, deviation)| {
81 let y = y[0].clamp(0.0, 1.0);
82 let u = u[0].clamp(0.0, 1.0);
83 let v = v[0].clamp(0.0, 1.0);
84 let color_diff = ((u).powi(2) + (v).powi(2)).sqrt().clamp(0.0, 1.0);
85 *deviation += y.min(color_diff);
87 *rgb = Rgb([1. - y, 1. - u, 1. - v]);
88 });
89
90 let score = deviation.iter().map(|s| *s as f64).sum::<f64>() / deviation.len() as f64;
91 Similarity {
92 image: image.into(),
93 score,
94 }
95}
96
97pub fn rgba_hybrid_compare(
103 first: &RgbaImage,
104 second: &RgbaImage,
105) -> Result<Similarity, CompareError> {
106 if first.dimensions() != second.dimensions() {
107 return Err(CompareError::DimensionsDiffer);
108 }
109
110 let first = split_rgba_to_yuva(first);
111 let second = split_rgba_to_yuva(second);
112
113 let (_, mssim_result) = ssim_simple(&first[0], &second[0])?;
114 let (_, u_result) = root_mean_squared_error_simple(&first[1], &second[1])?;
115 let (_, v_result) = root_mean_squared_error_simple(&first[2], &second[2])?;
116
117 let (_, alpha_result) = root_mean_squared_error_simple(&first[3], &second[3])?;
118
119 let results = [mssim_result, u_result, v_result, alpha_result];
120
121 Ok(merge_similarity_channels_yuva(
122 &results, &first[3], &second[3],
123 ))
124}
125
126pub enum BlendInput<'a> {
128 PreBlended(&'a RgbImage),
130 RGBA(&'a RgbaImage),
132}
133
134impl<'a> BlendInput<'a> {
135 fn into_blended(self, background: Rgb<u8>) -> Cow<'a, RgbImage> {
136 match self {
137 BlendInput::PreBlended(image) => Cow::Borrowed(image),
138 BlendInput::RGBA(rgba) => Cow::Owned(blend_alpha(rgba, background)),
139 }
140 }
141}
142
143impl<'a> From<&'a RgbImage> for BlendInput<'a> {
144 fn from(value: &'a RgbImage) -> Self {
145 BlendInput::PreBlended(value)
146 }
147}
148
149impl<'a> From<&'a RgbaImage> for BlendInput<'a> {
150 fn from(value: &'a RgbaImage) -> Self {
151 BlendInput::RGBA(value)
152 }
153}
154
155pub fn rgba_blended_hybrid_compare(
158 first: BlendInput,
159 second: BlendInput,
160 background: Rgb<u8>,
161) -> Result<Similarity, CompareError> {
162 let first = first.into_blended(background);
163 let second = second.into_blended(background);
164 rgb_hybrid_compare(&first, &second)
165}
166
167pub fn rgb_hybrid_compare(first: &RgbImage, second: &RgbImage) -> Result<Similarity, CompareError> {
176 if first.dimensions() != second.dimensions() {
177 return Err(CompareError::DimensionsDiffer);
178 }
179
180 let first_channels = first.split_to_yuv();
181 let second_channels = second.split_to_yuv();
182
183 internal_yuv_hybrid_compare(&first_channels, &second_channels)
184}
185
186#[cfg(feature = "yuv_compare")]
199pub fn yuv_hybrid_compare(first_channels: &[GrayImage; 3], second_channels: &[GrayImage; 3]) -> Result<Similarity, CompareError> {
200 if (first_channels[0].dimensions() != second_channels[0].dimensions())
201 || (first_channels[1].dimensions() != second_channels[1].dimensions()) || (first_channels[2].dimensions() != second_channels[2].dimensions()) || (first_channels[0].dimensions() != first_channels[1].dimensions()) || (first_channels[1].dimensions() != first_channels[2].dimensions()) { return Err(CompareError::DimensionsDiffer);
206 }
207 internal_yuv_hybrid_compare(&first_channels, &second_channels)
208}
209
210pub(crate) fn internal_yuv_hybrid_compare(first_channels: &[GrayImage; 3], second_channels: &[GrayImage; 3]) -> Result<Similarity, CompareError> {
211 let (_, mssim_result) = ssim_simple(&first_channels[0], &second_channels[0])?;
212 let (_, u_result) = root_mean_squared_error_simple(&first_channels[1], &second_channels[1])?;
213 let (_, v_result) = root_mean_squared_error_simple(&first_channels[2], &second_channels[2])?;
214
215 let results = [mssim_result, u_result, v_result];
216
217 Ok(merge_similarity_channels_yuv(&results))
218}