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(
53 curr_pixels: &[Rgb],
54 prev_pixels: &[Rgb],
55 options: &DeltaOptions,
56) -> Option<FrameDelta> {
57 if curr_pixels.len() != prev_pixels.len() {
58 return None;
59 }
60
61 let mut min_x = options.width;
62 let mut max_x = 0;
63 let mut min_y = options.height;
64 let mut max_y = 0;
65 let mut changed = false;
66
67 for y in 0..options.height {
69 for x in 0..options.width {
70 let idx = (y as usize * options.width as usize) + x as usize;
71 if rgb_dist_sq(curr_pixels[idx], prev_pixels[idx]) > options.fuzz_threshold {
72 if x < min_x {
73 min_x = x;
74 }
75 if x > max_x {
76 max_x = x;
77 }
78 if y < min_y {
79 min_y = y;
80 }
81 if y > max_y {
82 max_y = y;
83 }
84 changed = true;
85 }
86 }
87 }
88
89 if !changed {
90 return None;
91 }
92
93 let delta_width = max_x - min_x + 1;
94 let delta_height = max_y - min_y + 1;
95
96 let lab_palette = if options.dither == DitherType::BlueNoise {
100 let lp: Vec<crate::color::Lab> = options.palette
101 .iter()
102 .map(|p| crate::color::rgb_to_lab(p.r, p.g, p.b))
103 .collect();
104 let pp = crate::simd::PlanarLabPalette::from_lab(&lp);
105 Some((lp, pp))
106 } else {
107 None
108 };
109
110 #[cfg(feature = "rayon")]
111 use rayon::prelude::*;
112
113 #[cfg(feature = "rayon")]
114 let delta_indices: Vec<u8> = (min_y..=max_y)
115 .into_par_iter()
116 .flat_map(|y| {
117 let lab_palette_ref = lab_palette.as_ref();
118 (min_x..=max_x).into_par_iter().map(move |x| {
119 let idx = (y as usize * options.width as usize) + x as usize;
120 if rgb_dist_sq(curr_pixels[idx], prev_pixels[idx]) <= options.fuzz_threshold {
122 options.transparent_idx
123 } else {
124 match options.dither {
125 DitherType::BlueNoise => {
126 if let Some((_, pp)) = lab_palette_ref {
127 let (ol, oa, ob) =
128 crate::quant::dither::get_blue_noise_offset(x, y);
129 let p = curr_pixels[idx];
130 let mut lab = crate::color::rgb_to_lab(p.r, p.g, p.b);
131 lab.l = (lab.l + ol).clamp(0.0, 100.0);
132 lab.a = (lab.a + oa).clamp(-128.0, 127.0);
133 lab.b = (lab.b + ob).clamp(-128.0, 127.0);
134 crate::simd::find_nearest_color_lab(lab, pp) as u8
135 } else {
136 crate::simd::find_nearest_color(curr_pixels[idx], options.palette) as u8
137 }
138 }
139 _ => crate::simd::find_nearest_color(curr_pixels[idx], options.palette) as u8,
140 }
141 }
142 })
143 })
144 .collect();
145
146 #[cfg(not(feature = "rayon"))]
147 let delta_indices: Vec<u8> = (min_y..=max_y)
148 .flat_map(|y| {
149 let lab_palette_ref = lab_palette.as_ref();
150 (min_x..=max_x).map(move |x| {
151 let idx = (y as usize * options.width as usize) + x as usize;
152 if rgb_dist_sq(curr_pixels[idx], prev_pixels[idx]) <= options.fuzz_threshold {
153 options.transparent_idx
154 } else {
155 match options.dither {
156 DitherType::BlueNoise => {
157 if let Some((_, pp)) = lab_palette_ref {
158 let (ol, oa, ob) =
159 crate::quant::dither::get_blue_noise_offset(x, y);
160 let p = curr_pixels[idx];
161 let mut lab = crate::color::rgb_to_lab(p.r, p.g, p.b);
162 lab.l = (lab.l + ol).clamp(0.0, 100.0);
163 lab.a = (lab.a + oa).clamp(-128.0, 127.0);
164 lab.b = (lab.b + ob).clamp(-128.0, 127.0);
165 crate::simd::find_nearest_color_lab(lab, pp) as u8
166 } else {
167 crate::simd::find_nearest_color(curr_pixels[idx], options.palette) as u8
168 }
169 }
170 _ => crate::simd::find_nearest_color(curr_pixels[idx], options.palette) as u8,
171 }
172 }
173 })
174 })
175 .collect();
176 Some(FrameDelta {
177 x: min_x,
178 y: min_y,
179 width: delta_width,
180 height: delta_height,
181 indices: delta_indices,
182 })
183}