1use crate::quant::{DitherType, Rgb};
4
5#[derive(Debug, Default)]
7pub struct FrameDelta {
8 pub x: u16,
10 pub y: u16,
12 pub width: u16,
14 pub height: u16,
16 pub indices: Vec<u8>,
18}
19
20#[inline]
22fn rgb_dist_sq(c1: Rgb, c2: Rgb) -> u32 {
23 let dr = c1.r as i32 - c2.r as i32;
24 let dg = c1.g as i32 - c2.g as i32;
25 let db = c1.b as i32 - c2.b as i32;
26 (dr * dr + dg * dg + db * db) as u32
27}
28
29pub struct DeltaOptions<'a> {
31 pub width: u16,
33 pub height: u16,
35 pub palette: &'a [Rgb],
37 pub transparent_idx: u8,
39 pub fuzz_threshold: u32,
41 pub dither: DitherType,
43}
44
45pub fn find_delta_fuzzy(
54 curr_pixels: &[Rgb],
55 prev_pixels: &[Rgb],
56 prev_indices: Option<&[u8]>,
57 options: &DeltaOptions,
58) -> Option<FrameDelta> {
59 if curr_pixels.len() != prev_pixels.len() {
60 return None;
61 }
62
63 let mut min_x = options.width;
64 let mut max_x = 0;
65 let mut min_y = options.height;
66 let mut max_y = 0;
67 let mut changed = false;
68
69 for y in 0..options.height {
71 for x in 0..options.width {
72 let idx = (y as usize * options.width as usize) + x as usize;
73 if rgb_dist_sq(curr_pixels[idx], prev_pixels[idx]) > options.fuzz_threshold {
74 if x < min_x {
75 min_x = x;
76 }
77 if x > max_x {
78 max_x = x;
79 }
80 if y < min_y {
81 min_y = y;
82 }
83 if y > max_y {
84 max_y = y;
85 }
86 changed = true;
87 }
88 }
89 }
90
91 if !changed {
92 return None;
93 }
94
95 let delta_width = max_x - min_x + 1;
96 let delta_height = max_y - min_y + 1;
97
98 let lab_palette = if options.dither == DitherType::BlueNoise {
102 let lp: Vec<crate::color::Lab> = options.palette
103 .iter()
104 .map(|p| crate::color::rgb_to_lab(p.r, p.g, p.b))
105 .collect();
106 let pp = crate::simd::PlanarLabPalette::from_lab(&lp);
107 Some((lp, pp))
108 } else {
109 None
110 };
111
112 #[cfg(feature = "rayon")]
113 use rayon::prelude::*;
114
115 #[cfg(feature = "rayon")]
116 let delta_indices: Vec<u8> = (min_y..=max_y)
117 .into_par_iter()
118 .flat_map(|y| {
119 let lab_palette_ref = lab_palette.as_ref();
120 (min_x..=max_x).into_par_iter().map(move |x| {
121 let idx = (y as usize * options.width as usize) + x as usize;
122 if rgb_dist_sq(curr_pixels[idx], prev_pixels[idx]) <= options.fuzz_threshold {
124 options.transparent_idx
125 } else {
126 if let Some(prev_idx_buffer) = prev_indices {
130 let prev_idx = prev_idx_buffer[idx];
131 if prev_idx != options.transparent_idx {
132 let prev_color = options.palette[prev_idx as usize];
133 if rgb_dist_sq(curr_pixels[idx], prev_color) <= options.fuzz_threshold / 2 {
135 return prev_idx;
136 }
137 }
138 }
139
140 match options.dither {
141 DitherType::BlueNoise => {
142 if let Some((_, pp)) = lab_palette_ref {
143 let (ol, oa, ob) =
144 crate::quant::dither::get_blue_noise_offset(x, y);
145 let p = curr_pixels[idx];
146 let mut lab = crate::color::rgb_to_lab(p.r, p.g, p.b);
147 lab.l = (lab.l + ol).clamp(0.0, 100.0);
148 lab.a = (lab.a + oa).clamp(-128.0, 127.0);
149 lab.b = (lab.b + ob).clamp(-128.0, 127.0);
150 crate::simd::find_nearest_color_lab(lab, pp) as u8
151 } else {
152 crate::simd::find_nearest_color(curr_pixels[idx], options.palette) as u8
153 }
154 }
155 _ => crate::simd::find_nearest_color(curr_pixels[idx], options.palette) as u8,
156 }
157 }
158 })
159 })
160 .collect();
161
162 #[cfg(not(feature = "rayon"))]
163 let delta_indices: Vec<u8> = (min_y..=max_y)
164 .flat_map(|y| {
165 let lab_palette_ref = lab_palette.as_ref();
166 (min_x..=max_x).map(move |x| {
167 let idx = (y as usize * options.width as usize) + x as usize;
168 if rgb_dist_sq(curr_pixels[idx], prev_pixels[idx]) <= options.fuzz_threshold {
169 options.transparent_idx
170 } else {
171 if let Some(prev_idx_buffer) = prev_indices {
173 let prev_idx = prev_idx_buffer[idx];
174 if prev_idx != options.transparent_idx {
175 let prev_color = options.palette[prev_idx as usize];
176 if rgb_dist_sq(curr_pixels[idx], prev_color) <= options.fuzz_threshold / 2 {
177 return prev_idx;
178 }
179 }
180 }
181
182 match options.dither {
183 DitherType::BlueNoise => {
184 if let Some((_, pp)) = lab_palette_ref {
185 let (ol, oa, ob) =
186 crate::quant::dither::get_blue_noise_offset(x, y);
187 let p = curr_pixels[idx];
188 let mut lab = crate::color::rgb_to_lab(p.r, p.g, p.b);
189 lab.l = (lab.l + ol).clamp(0.0, 100.0);
190 lab.a = (lab.a + oa).clamp(-128.0, 127.0);
191 lab.b = (lab.b + ob).clamp(-128.0, 127.0);
192 crate::simd::find_nearest_color_lab(lab, pp) as u8
193 } else {
194 crate::simd::find_nearest_color(curr_pixels[idx], options.palette) as u8
195 }
196 }
197 _ => crate::simd::find_nearest_color(curr_pixels[idx], options.palette) as u8,
198 }
199 }
200 })
201 })
202 .collect();
203 Some(FrameDelta {
204 x: min_x,
205 y: min_y,
206 width: delta_width,
207 height: delta_height,
208 indices: delta_indices,
209 })
210}