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}