Skip to main content

figif_core/traits/
similarity.rs

1//! Similarity metric trait for custom frame comparison.
2
3use image::RgbaImage;
4
5/// Trait for computing similarity between frames.
6///
7/// This provides an alternative to hash-based comparison for cases
8/// where more precise similarity measurement is needed.
9///
10/// # Example
11///
12/// ```ignore
13/// use figif_core::traits::SimilarityMetric;
14///
15/// struct MSEMetric;
16///
17/// impl SimilarityMetric for MSEMetric {
18///     fn similarity(&self, a: &RgbaImage, b: &RgbaImage) -> f64 {
19///         // Compute mean squared error and convert to similarity
20///         let mse = compute_mse(a, b);
21///         1.0 / (1.0 + mse)
22///     }
23///
24///     fn duplicate_threshold(&self) -> f64 {
25///         0.95 // 95% similarity = duplicate
26///     }
27/// }
28/// ```
29pub trait SimilarityMetric: Send + Sync {
30    /// Compute similarity between two frames.
31    ///
32    /// Returns a value between 0.0 and 1.0:
33    /// - 1.0 = identical
34    /// - 0.0 = completely different
35    fn similarity(&self, a: &RgbaImage, b: &RgbaImage) -> f64;
36
37    /// The threshold above which frames are considered duplicates.
38    ///
39    /// Frames with `similarity >= duplicate_threshold()` are considered duplicates.
40    fn duplicate_threshold(&self) -> f64;
41
42    /// Get the name of this metric for logging/debugging.
43    fn name(&self) -> &'static str;
44
45    /// Check if two frames are duplicates.
46    fn are_duplicates(&self, a: &RgbaImage, b: &RgbaImage) -> bool {
47        self.similarity(a, b) >= self.duplicate_threshold()
48    }
49}
50
51/// A similarity metric based on a hash distance threshold.
52///
53/// This adapter allows using a `FrameHasher` as a `SimilarityMetric`.
54pub struct HashBasedSimilarity<H> {
55    hasher: H,
56    max_distance: u32,
57}
58
59impl<H> HashBasedSimilarity<H> {
60    /// Create a new hash-based similarity metric.
61    ///
62    /// `max_distance` is the maximum hash distance that maps to similarity > 0.
63    pub fn new(hasher: H, max_distance: u32) -> Self {
64        Self {
65            hasher,
66            max_distance,
67        }
68    }
69}
70
71impl<H> SimilarityMetric for HashBasedSimilarity<H>
72where
73    H: crate::traits::FrameHasher,
74{
75    fn similarity(&self, a: &RgbaImage, b: &RgbaImage) -> f64 {
76        let hash_a = self.hasher.hash_frame(a);
77        let hash_b = self.hasher.hash_frame(b);
78        let distance = self.hasher.distance(&hash_a, &hash_b);
79
80        // Convert distance to similarity (0 distance = 1.0 similarity)
81        if distance >= self.max_distance {
82            0.0
83        } else {
84            1.0 - (distance as f64 / self.max_distance as f64)
85        }
86    }
87
88    fn duplicate_threshold(&self) -> f64 {
89        // Map suggested threshold to similarity
90        let threshold = self.hasher.suggested_threshold();
91        1.0 - (threshold as f64 / self.max_distance as f64)
92    }
93
94    fn name(&self) -> &'static str {
95        "hash-based"
96    }
97}