1#![allow(non_upper_case_globals)]
2#![allow(non_snake_case)]
3use crate::blur;
23use crate::image::*;
24use crate::linear::ToRGBAPLU;
25pub use crate::tolab::ToLABBitmap;
26pub use crate::val::Dssim as Val;
27use imgref::*;
28#[cfg(not(feature = "threads"))]
29use crate::lieon as rayon;
30use rayon::prelude::*;
31use rgb::{RGB, RGBA};
32use std::borrow::Borrow;
33use std::mem::MaybeUninit;
34use std::ops;
35use std::ops::Deref;
36use std::sync::Arc;
37
38trait Channable<T, I> {
39 fn img1_img2_blur(&self, modified: &Self, tmp: &mut [MaybeUninit<I>]) -> Vec<T>;
40}
41
42#[derive(Clone)]
43struct DssimChan<T> {
44 pub width: usize,
45 pub height: usize,
46 pub img: Option<ImgVec<T>>,
47 pub mu: Vec<T>,
48 pub img_sq_blur: Vec<T>,
49 pub is_chroma: bool,
50}
51
52#[derive(Clone, Debug)]
54pub struct Dssim {
55 scale_weights: Vec<f64>,
56 save_maps_scales: u8,
57}
58
59#[derive(Clone)]
60struct DssimChanScale<T> {
61 chan: Vec<DssimChan<T>>,
62}
63
64#[derive(Clone)]
66pub struct DssimImage<T> {
67 scale: Vec<DssimChanScale<T>>,
68}
69
70impl<T> DssimImage<T> {
71 #[inline]
72 #[must_use]
73 pub fn width(&self) -> usize {
74 self.scale[0].chan[0].width
75 }
76
77 #[inline]
78 #[must_use]
79 pub fn height(&self) -> usize {
80 self.scale[0].chan[0].height
81 }
82}
83
84const DEFAULT_WEIGHTS: [f64; 5] = [0.028, 0.197, 0.322, 0.298, 0.155];
86
87#[derive(Clone)]
89pub struct SsimMap {
90 pub map: ImgVec<f32>,
92 pub ssim: f64,
94}
95
96#[must_use]
98pub fn new() -> Dssim {
99 Dssim::new()
100}
101
102impl DssimChan<f32> {
103 pub fn new(bitmap: ImgVec<f32>, is_chroma: bool) -> Self {
104 debug_assert!(bitmap.pixels().all(|i| i.is_finite() && i >= 0.0 && i <= 1.0));
105
106 Self {
107 width: bitmap.width(),
108 height: bitmap.height(),
109 mu: Vec::new(),
110 img: Some(bitmap),
111 img_sq_blur: Vec::new(),
112 is_chroma,
113 }
114 }
115}
116
117impl DssimChan<f32> {
118 fn preprocess(&mut self, tmp: &mut [MaybeUninit<f32>]) {
119 let width = self.width;
120 let height = self.height;
121 assert!(width > 0);
122 assert!(height > 0);
123
124 let img = self.img.as_mut().unwrap();
125 debug_assert_eq!(width * height, img.pixels().count());
126 debug_assert!(img.pixels().all(f32::is_finite));
127
128 if self.is_chroma {
129 blur::blur_in_place(img.as_mut(), tmp);
130 }
131 let (mu, ..) = blur::blur(img.as_ref(), tmp).into_contiguous_buf();
132 self.mu = mu;
133
134 self.img_sq_blur = blur::blur_mul(img.as_ref(), img.as_ref(), tmp);
138 debug_assert_eq!(self.img_sq_blur.len(), width * height);
139 }
140}
141
142impl Channable<f32, f32> for DssimChan<f32> {
143 fn img1_img2_blur(&self, modified: &Self, tmp32: &mut [MaybeUninit<f32>]) -> Vec<f32> {
144 let src = self.img.as_ref().unwrap();
145 let modified_img = modified.img.as_ref().unwrap();
146 blur::blur_mul(src.as_ref(), modified_img.as_ref(), tmp32)
148 }
149}
150
151impl Dssim {
152 #[must_use]
154 pub fn new() -> Self {
155 Self {
156 scale_weights: DEFAULT_WEIGHTS[..].to_owned(),
157 save_maps_scales: 0,
158 }
159 }
160
161 pub fn set_scales(&mut self, scales: &[f64]) {
163 self.scale_weights = scales.to_vec();
164 }
165
166 pub fn set_save_ssim_maps(&mut self, num_scales: u8) {
168 self.save_maps_scales = num_scales;
169 }
170
171 #[must_use]
175 pub fn create_image_rgba(&self, bitmap: &[RGBA<u8>], width: usize, height: usize) -> Option<DssimImage<f32>> {
176 if width * height < bitmap.len() {
177 return None;
178 }
179 let img = ImgVec::new(bitmap.to_rgbaplu(), width, height);
180 self.create_image(&img)
181 }
182
183 #[must_use]
187 pub fn create_image_rgb(&self, bitmap: &[RGB<u8>], width: usize, height: usize) -> Option<DssimImage<f32>> {
188 if width * height < bitmap.len() {
189 return None;
190 }
191 let img = ImgVec::new(bitmap.to_rgblu(), width, height);
192 self.create_image(&img)
193 }
194
195 pub fn create_image<InBitmap, OutBitmap>(&self, src_img: &InBitmap) -> Option<DssimImage<f32>>
206 where
207 InBitmap: ToLABBitmap + Send + Sync + Downsample<Output = OutBitmap>,
208 OutBitmap: ToLABBitmap + Send + Sync + Downsample<Output = OutBitmap>,
209 {
210 let num_scales = self.scale_weights.len();
211 let mut scale = Vec::with_capacity(num_scales);
212 Self::make_scales_recursive(num_scales, MaybeArc::Borrowed(src_img), &mut scale);
213 scale.reverse(); Some(DssimImage { scale })
216 }
217
218 #[inline(never)]
219 fn make_scales_recursive<InBitmap, OutBitmap>(scales_left: usize, image: MaybeArc<'_, InBitmap>, scales: &mut Vec<DssimChanScale<f32>>)
220 where
221 InBitmap: ToLABBitmap + Send + Sync + Downsample<Output = OutBitmap>,
222 OutBitmap: ToLABBitmap + Send + Sync + Downsample<Output = OutBitmap>,
223 {
224 let (chan, _) = rayon::join({
226 let image = image.clone();
227 move || {
228 let lab = image.to_lab();
229 drop(image); DssimChanScale {
231 chan: lab.into_par_iter().with_max_len(1).enumerate().map(|(n,l)| {
232 let w = l.width();
233 let h = l.height();
234 let mut ch = DssimChan::new(l, n > 0);
235
236 let pixels = w * h;
237 let mut tmp = Vec::with_capacity(pixels);
238 ch.preprocess(&mut tmp.spare_capacity_mut()[..pixels]);
239 ch
240 }).collect(),
241 }
242 }
243 }, {
244 let scales = &mut *scales;
245 move || {
246 if scales_left > 0 {
247 let down = image.downsample();
248 drop(image);
249 if let Some(downsampled) = down {
250 Self::make_scales_recursive(scales_left - 1, MaybeArc::Owned(Arc::new(downsampled)), scales);
251 }
252 }
253 }
254 });
255 scales.push(chan);
256 }
257
258 pub fn compare<M: Borrow<DssimImage<f32>>>(&self, original_image: &DssimImage<f32>, modified_image: M) -> (Val, Vec<SsimMap>) {
264 self.compare_inner(original_image, modified_image.borrow())
265 }
266
267 #[inline(never)]
268 fn compare_inner(&self, original_image: &DssimImage<f32>, modified_image: &DssimImage<f32>) -> (Val, Vec<SsimMap>) {
269 let scaled_images_iter = modified_image.scale.iter().zip(original_image.scale.iter());
270 let combined_iter = self.scale_weights.iter().copied().zip(scaled_images_iter).enumerate();
271
272 let res: Vec<_> = combined_iter.par_bridge().map(|(n, (weight, (modified_image_scale, original_image_scale)))| {
273 let scale_width = original_image_scale.chan[0].width;
274 let scale_height = original_image_scale.chan[0].height;
275 let pixels = scale_width * scale_height;
276
277 let ssim_map = match original_image_scale.chan.len() {
278 3 => {
279 let img1_img2_blur: Vec<Vec<f32>> = (0..3usize).into_par_iter().map(|c| {
283 let mut tmp_buf: Vec<f32> = Vec::with_capacity(pixels);
284 let tmp = &mut tmp_buf.spare_capacity_mut()[..pixels];
285 original_image_scale.chan[c]
286 .img1_img2_blur(&modified_image_scale.chan[c], tmp)
287 }).collect();
288 Self::compare_scale_3ch(original_image_scale, modified_image_scale, &img1_img2_blur)
289 },
290 1 => {
291 let mut tmp_buf: Vec<f32> = Vec::with_capacity(pixels);
292 let tmp = &mut tmp_buf.spare_capacity_mut()[..pixels];
293 let img1_img2_blur = original_image_scale.chan[0].img1_img2_blur(&modified_image_scale.chan[0], tmp);
294 Self::compare_scale(&original_image_scale.chan[0], &modified_image_scale.chan[0], &img1_img2_blur)
295 },
296 _ => panic!(),
297 };
298
299 let sum = ssim_map.pixels().fold(0., |sum, i| sum + f64::from(i));
300 let len = (ssim_map.width()*ssim_map.height()) as f64;
301 let avg = (sum / len).max(0.0).powf((0.5_f64).powf(n as f64));
302 let score = 1.0 - (ssim_map.pixels().fold(0., |sum, i| sum + (avg - f64::from(i)).abs()) / len);
303
304 let map = if self.save_maps_scales as usize > n {
305 Some(SsimMap {
306 map: ssim_map,
307 ssim: score,
308 })
309 } else {
310 None
311 };
312 (score, weight, map)
313 }).collect();
314
315 let mut ssim_sum = 0.0;
316 let mut weight_sum = 0.0;
317 let mut ssim_maps = Vec::new();
318 for (score, weight, map) in res {
319 ssim_sum = score.mul_add(weight, ssim_sum);
320 weight_sum += weight;
321 if let Some(m) = map {
322 ssim_maps.push(m);
323 }
324 }
325
326 (to_dssim(ssim_sum / weight_sum).into(), ssim_maps)
327 }
328
329 #[inline(never)]
335 fn compare_scale_3ch(
336 original: &DssimChanScale<f32>,
337 modified: &DssimChanScale<f32>,
338 img1_img2_blur: &[Vec<f32>],
339 ) -> ImgVec<f32> {
340 let width = original.chan[0].width;
341 let height = original.chan[0].height;
342 let pixels = width * height;
343
344 let (o0, o1, o2) = (&original.chan[0], &original.chan[1], &original.chan[2]);
345 let (m0, m1, m2) = (&modified.chan[0], &modified.chan[1], &modified.chan[2]);
346
347 let o0_mu = &o0.mu[..pixels];
348 let o1_mu = &o1.mu[..pixels];
349 let o2_mu = &o2.mu[..pixels];
350 let m0_mu = &m0.mu[..pixels];
351 let m1_mu = &m1.mu[..pixels];
352 let m2_mu = &m2.mu[..pixels];
353 let o0_sq = &o0.img_sq_blur[..pixels];
354 let o1_sq = &o1.img_sq_blur[..pixels];
355 let o2_sq = &o2.img_sq_blur[..pixels];
356 let m0_sq = &m0.img_sq_blur[..pixels];
357 let m1_sq = &m1.img_sq_blur[..pixels];
358 let m2_sq = &m2.img_sq_blur[..pixels];
359 let i12_0 = &img1_img2_blur[0][..pixels];
360 let i12_1 = &img1_img2_blur[1][..pixels];
361 let i12_2 = &img1_img2_blur[2][..pixels];
362
363 let c1: f32 = 0.01 * 0.01;
364 let c2: f32 = 0.03 * 0.03;
365 let inv3: f32 = 1.0 / 3.0;
366
367 let map_out: Vec<f32> = (0..pixels).into_par_iter().with_min_len(1 << 10).map(|i| {
368 let mu1_0 = o0_mu[i]; let mu2_0 = m0_mu[i];
369 let mu1_1 = o1_mu[i]; let mu2_1 = m1_mu[i];
370 let mu1_2 = o2_mu[i]; let mu2_2 = m2_mu[i];
371
372 let mu1mu1_0 = mu1_0 * mu1_0;
373 let mu1mu1_1 = mu1_1 * mu1_1;
374 let mu1mu1_2 = mu1_2 * mu1_2;
375 let mu2mu2_0 = mu2_0 * mu2_0;
376 let mu2mu2_1 = mu2_1 * mu2_1;
377 let mu2mu2_2 = mu2_2 * mu2_2;
378 let mu1mu2_0 = mu1_0 * mu2_0;
379 let mu1mu2_1 = mu1_1 * mu2_1;
380 let mu1mu2_2 = mu1_2 * mu2_2;
381
382 let mu1_sq = (mu1mu1_0 + mu1mu1_1 + mu1mu1_2) * inv3;
383 let mu2_sq = (mu2mu2_0 + mu2mu2_1 + mu2mu2_2) * inv3;
384 let mu1_mu2 = (mu1mu2_0 + mu1mu2_1 + mu1mu2_2) * inv3;
385
386 let sigma1_sq = ((o0_sq[i] - mu1mu1_0) + (o1_sq[i] - mu1mu1_1) + (o2_sq[i] - mu1mu1_2)) * inv3;
387 let sigma2_sq = ((m0_sq[i] - mu2mu2_0) + (m1_sq[i] - mu2mu2_1) + (m2_sq[i] - mu2mu2_2)) * inv3;
388 let sigma12 = ((i12_0[i] - mu1mu2_0) + (i12_1[i] - mu1mu2_1) + (i12_2[i] - mu1mu2_2)) * inv3;
389
390 2.0f32.mul_add(mu1_mu2, c1) * 2.0f32.mul_add(sigma12, c2)
391 / ((mu1_sq + mu2_sq + c1) * (sigma1_sq + sigma2_sq + c2))
392 }).collect();
393
394 ImgVec::new(map_out, width, height)
395 }
396
397 #[inline(never)]
398 fn compare_scale<L>(original: &DssimChan<L>, modified: &DssimChan<L>, img1_img2_blur: &[L]) -> ImgVec<f32>
399 where
400 L: Send + Sync + Clone + Copy + ops::Mul<Output = L> + ops::Sub<Output = L> + 'static,
401 f32: From<L>,
402 {
403 assert_eq!(original.width, modified.width);
404 assert_eq!(original.height, modified.height);
405
406 let width = original.width;
407 let height = original.height;
408
409 let c1 = 0.01 * 0.01;
410 let c2 = 0.03 * 0.03;
411
412 debug_assert_eq!(original.mu.len(), modified.mu.len());
413 debug_assert_eq!(original.img_sq_blur.len(), modified.img_sq_blur.len());
414 debug_assert_eq!(img1_img2_blur.len(), original.mu.len());
415 debug_assert_eq!(img1_img2_blur.len(), original.img_sq_blur.len());
416
417 let mu_iter = original.mu.as_slice().par_iter().with_min_len(1<<10).cloned().zip_eq(modified.mu.as_slice().par_iter().with_min_len(1<<10).cloned());
418 let sq_iter = original.img_sq_blur.as_slice().par_iter().with_min_len(1<<10).cloned().zip_eq(modified.img_sq_blur.as_slice().par_iter().with_min_len(1<<10).cloned());
419 let map_out = img1_img2_blur.par_iter().with_min_len(1<<10).cloned().zip_eq(mu_iter).zip_eq(sq_iter)
420 .map(|((img1_img2_blur, (mu1, mu2)), (img1_sq_blur, img2_sq_blur))| {
421 let mu1mu1 = mu1 * mu1;
422 let mu1mu2 = mu1 * mu2;
423 let mu2mu2 = mu2 * mu2;
424 let mu1_sq: f32 = mu1mu1.into();
425 let mu2_sq: f32 = mu2mu2.into();
426 let mu1_mu2: f32 = mu1mu2.into();
427 let sigma1_sq: f32 = (img1_sq_blur - mu1mu1).into();
428 let sigma2_sq: f32 = (img2_sq_blur - mu2mu2).into();
429 let sigma12: f32 = (img1_img2_blur - mu1mu2).into();
430
431 2.0f32.mul_add(mu1_mu2, c1) * 2.0f32.mul_add(sigma12, c2) /
432 ((mu1_sq + mu2_sq + c1) * (sigma1_sq + sigma2_sq + c2))
433 }).collect();
434
435 ImgVec::new(map_out, width, height)
436 }
437}
438
439fn to_dssim(ssim: f64) -> f64 {
440 1.0 / ssim.max(f64::EPSILON) - 1.0
441}
442
443#[test]
444fn png_compare() {
445 use crate::linear::*;
446 use imgref::*;
447
448 let d = new();
449 let file1 = lodepng::decode32_file("../tests/test1-sm.png").unwrap();
450 let file2 = lodepng::decode32_file("../tests/test2-sm.png").unwrap();
451
452 let buf1 = &file1.buffer.to_rgbaplu()[..];
453 let buf2 = &file2.buffer.to_rgbaplu()[..];
454 let img1 = d.create_image(&Img::new(buf1, file1.width, file1.height)).unwrap();
455 let img2 = d.create_image(&Img::new(buf2, file2.width, file2.height)).unwrap();
456
457 let (res, _) = d.compare(&img1, img2);
458 assert!((0.001 - res).abs() < 0.0005, "res is {res}");
459
460 let img1b = d.create_image(&Img::new(buf1, file1.width, file1.height)).unwrap();
461 let (res, _) = d.compare(&img1, img1b);
462
463 assert!(0.000000000000001 > res);
464 assert!(res < 0.000000000000001);
465 assert_eq!(res, res);
466
467 let sub_img1 = d.create_image(&Img::new(buf1, file1.width, file1.height).sub_image(2,3,44,33)).unwrap();
468 let sub_img2 = d.create_image(&Img::new(buf2, file2.width, file2.height).sub_image(17,9,44,33)).unwrap();
469 let (res, _) = d.compare(&sub_img1, sub_img2);
471 assert!(res > 0.1);
472
473 let sub_img1 = d.create_image(&Img::new(buf1, file1.width, file1.height).sub_image(22,8,61,40)).unwrap();
474 let sub_img2 = d.create_image(&Img::new(buf2, file2.width, file2.height).sub_image(22,8,61,40)).unwrap();
475 let (res, _) = d.compare(&sub_img1, sub_img2);
477 assert!(res < 0.01);
478}
479
480#[test]
500fn ssim_locked_values() {
501 use crate::linear::*;
502 use imgref::*;
503
504 const ABS_TOL: f64 = 5e-6;
506
507 fn approx_eq(name: &str, got: f64, expected: f64) {
508 let diff = (got - expected).abs();
509 assert!(
510 diff <= ABS_TOL,
511 "{name}: got {got}, expected {expected} (abs diff={diff:.3e}, allowed={ABS_TOL:.0e})",
512 );
513 }
514
515 let d = new();
516 let file1 = lodepng::decode32_file("../tests/test1-sm.png").unwrap();
517 let file2 = lodepng::decode32_file("../tests/test2-sm.png").unwrap();
518 let buf1 = &file1.buffer.to_rgbaplu()[..];
519 let buf2 = &file2.buffer.to_rgbaplu()[..];
520 let img1 = || Img::new(buf1, file1.width, file1.height);
521 let img2 = || Img::new(buf2, file2.width, file2.height);
522
523 let a = d.create_image(&img1()).unwrap();
527 let b = d.create_image(&img2()).unwrap();
528 let (got, _) = d.compare(&a, b);
529 approx_eq("full test1 vs test2", f64::from(got), 0.0009483923725199794);
530
531 let a2 = d.create_image(&img1()).unwrap();
534 let b2 = d.create_image(&img1()).unwrap();
535 let (got, _) = d.compare(&a2, b2);
536 assert_eq!(f64::from(got), 0.0, "identity must be exactly 0, got {got}");
537
538 let s1 = d.create_image(&img1().sub_image(2, 3, 44, 33)).unwrap();
541 let s2 = d.create_image(&img2().sub_image(17, 9, 44, 33)).unwrap();
542 let (got, _) = d.compare(&s1, s2);
543 approx_eq("sub [2,3,44x33] vs [17,9,44x33]", f64::from(got), 0.10810340934514495);
544
545 let s1 = d.create_image(&img1().sub_image(22, 8, 61, 40)).unwrap();
547 let s2 = d.create_image(&img2().sub_image(22, 8, 61, 40)).unwrap();
548 let (got, _) = d.compare(&s1, s2);
549 approx_eq("sub [22,8,61x40] aligned", f64::from(got), 0.001675780079775091);
550}
551
552enum MaybeArc<'a, T> {
553 Owned(Arc<T>),
554 Borrowed(&'a T),
555}
556
557impl<T> Clone for MaybeArc<'_, T> {
558 fn clone(&self) -> Self {
559 match self {
560 Self::Owned(t) => Self::Owned(t.clone()),
561 Self::Borrowed(t) => Self::Borrowed(t),
562 }
563 }
564}
565
566impl<T> Deref for MaybeArc<'_, T> {
567 type Target = T;
568
569 fn deref(&self) -> &Self::Target {
570 match self {
571 Self::Owned(t) => t,
572 Self::Borrowed(t) => t,
573 }
574 }
575}
576
577#[test]
578fn poison() {
579 let a = RGBAPLU::new(1.,1.,1.,1.);
580 let b = RGBAPLU::new(0.,0.,0.,0.);
581 let n = 1./0.;
582 let n = RGBAPLU::new(n,n,n,n);
583 let buf = vec![
584 b,a,a,b,n,n,
585 a,b,b,a,n,n,
586 b,a,a,b,n,
587 ];
588 let img = ImgVec::new_stride(buf, 4, 3, 6);
589 assert!(img.pixels().all(|p| p.r.is_finite() && p.a.is_finite()));
590 assert!(img.as_ref().pixels().all(|p| p.g.is_finite() && p.b.is_finite()));
591
592 let d = new();
593 let sub_img1 = d.create_image(&img.as_ref()).unwrap();
594 let sub_img2 = d.create_image(&img.as_ref()).unwrap();
595 let (res, _) = d.compare(&sub_img1, sub_img2);
596 assert!(res < 0.000001);
597}