1use base64::{decode, encode, DecodeError};
2use image::io::Reader;
3use image::DynamicImage;
4use image::DynamicImage::ImageRgba8;
5use image::GenericImageView;
6use image::ImageBuffer;
7use image::Rgba;
8use std::io::Cursor;
9use std::{cmp, vec};
10
11pub static BLACK: (u8, u8, u8) = (0, 0, 0);
12pub static RED: (u8, u8, u8) = (255, 119, 119);
13pub static GREEN: (u8, u8, u8) = (99, 195, 99);
14static RATE: f32 = 0.25;
15
16#[derive(Debug, PartialEq)]
17enum DiffResult<'a, T: PartialEq> {
18 Removed(DiffElement<'a, T>),
19 Common(DiffElement<'a, T>),
20 Added(DiffElement<'a, T>),
21}
22
23#[derive(Debug, PartialEq)]
24struct DiffElement<'a, T: PartialEq> {
25 pub data: &'a T,
26}
27
28pub fn create_table<T: PartialEq>(old: &[T], new: &[T]) -> Vec<Vec<u32>> {
34 let new_len = new.len();
35 let old_len = old.len();
36 let mut table = vec![vec![0; old_len + 1]; new_len + 1];
37 for i in 0..new_len {
38 let i = new_len - i - 1;
39 for j in 0..old_len {
40 let j = old_len - j - 1;
41 table[i][j] = if new[i] == old[j] {
43 table[i + 1][j + 1] + 1
44 } else {
45 cmp::max(table[i + 1][j], table[i][j + 1])
46 }
47 }
48 }
49 table
50}
51
52fn lcs_diff<'a, T: PartialEq>(old: &'a [T], new: &'a [T]) -> Vec<DiffResult<'a, T>> {
53 let new_len = new.len();
54 let old_len = old.len();
55
56 if new_len == 0 {
57 let mut result = Vec::with_capacity(old_len);
58 let mut o = 0;
59 while o < old_len {
60 result.push(DiffResult::Removed(DiffElement { data: &old[o] }));
61 o += 1;
62 }
63 return result;
64 } else if old_len == 0 {
65 let mut result = Vec::with_capacity(new_len);
66 let mut n = 0;
67 while n < new_len {
68 result.push(DiffResult::Added(DiffElement { data: &new[n] }));
69 n += 1;
70 }
71 return result;
72 } else {
73 let mut o = 0;
74 let mut n = 0;
75 let common_prefix = old.iter().zip(new).take_while(|p| p.0 == p.1);
76 let prefix_size = common_prefix.count();
77 let common_suffix = old
78 .iter()
79 .rev()
80 .zip(new.iter().rev())
81 .take(cmp::min(old_len, new_len) - prefix_size)
82 .take_while(|p| p.0 == p.1);
83 let suffix_size = common_suffix.count();
84 let table = create_table(
85 &old[prefix_size..(old_len - suffix_size)],
86 &new[prefix_size..(new_len - suffix_size)],
87 );
88 let new_len = new_len - prefix_size - suffix_size;
89 let old_len = old_len - prefix_size - suffix_size;
90 let mut result = Vec::with_capacity(prefix_size + cmp::max(old_len, new_len) + suffix_size);
91
92 let mut prefix_index = 0;
94 while prefix_index < prefix_size {
95 result.push(DiffResult::Common(DiffElement {
96 data: &old[prefix_index],
97 }));
98 prefix_index += 1;
99 }
100
101 loop {
102 if n >= new_len || o >= old_len {
103 break;
104 }
105 let new_index = n + prefix_size;
106 let old_index = o + prefix_size;
107 if new[new_index] == old[old_index] {
108 result.push(DiffResult::Common(DiffElement {
109 data: &new[new_index],
110 }));
111 n += 1;
112 o += 1;
113 } else if table[n + 1][o] >= table[n][o + 1] {
114 result.push(DiffResult::Added(DiffElement {
115 data: &new[new_index],
116 }));
117 n += 1;
118 } else {
119 result.push(DiffResult::Removed(DiffElement {
120 data: &old[old_index],
121 }));
122 o += 1;
123 }
124 }
125 while n < new_len {
126 let new_index = n + prefix_size;
127 result.push(DiffResult::Added(DiffElement {
128 data: &new[new_index],
129 }));
130 n += 1;
131 }
132 while o < old_len {
133 let old_index = o + prefix_size;
134 result.push(DiffResult::Removed(DiffElement {
135 data: &old[old_index],
136 }));
137 o += 1;
138 }
139
140 let mut suffix_index = 0;
142 while suffix_index < suffix_size {
143 let old_index = suffix_index + old_len + prefix_size;
144 result.push(DiffResult::Common(DiffElement {
145 data: &old[old_index],
146 }));
147 suffix_index += 1;
148 }
149 result
150 }
151}
152
153fn blend(base: Rgba<u8>, rgb: (u8, u8, u8), rate: f32) -> Rgba<u8> {
154 Rgba([
155 (base[0] as f32 * (1.0 - rate) + rgb.0 as f32 * (rate)) as u8,
156 (base[1] as f32 * (1.0 - rate) + rgb.1 as f32 * (rate)) as u8,
157 (base[2] as f32 * (1.0 - rate) + rgb.2 as f32 * (rate)) as u8,
158 base[3],
159 ])
160}
161
162fn put_diff_pixels(
163 y: usize,
164 img: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
165 row_width: u32,
166 data: &str,
167 rgb: (u8, u8, u8),
168 rate: f32,
169) -> Result<(), base64::DecodeError> {
170 let row = decode(data)?;
171 for x in 0..img.dimensions().0 {
172 let index = x as usize * 4;
173 let pixel = if row_width > x {
174 Rgba([row[index], row[index + 1], row[index + 2], row[index + 3]])
175 } else {
176 Rgba([0, 0, 0, 0])
177 };
178 img.put_pixel(x as u32, y as u32, blend(pixel, rgb, rate));
179 }
180 Ok(())
181}
182
183pub fn diff(
184 before_png: &DynamicImage,
185 after_png: &DynamicImage,
186) -> Result<DynamicImage, DecodeError> {
187 let after_w = after_png.dimensions().0;
188 let before_w = before_png.dimensions().0;
189 let before_encoded_png: Vec<String> = before_png
190 .as_bytes()
191 .chunks(before_w as usize * 4)
192 .map(encode)
193 .collect();
194 let after_encoded_png: Vec<String> = after_png
195 .as_bytes()
196 .chunks(after_w as usize * 4)
197 .map(encode)
198 .collect();
199
200 let diff_result = lcs_diff(&before_encoded_png, &after_encoded_png);
201
202 let height = diff_result.len() as u32;
203 let width = cmp::max(before_w, after_w) as u32;
204 let mut img = ImageBuffer::new(width, height);
205 for (y, d) in diff_result.iter().enumerate() {
206 match d {
207 DiffResult::Added(ref a) => {
208 put_diff_pixels(y, &mut img, after_w as u32, a.data, GREEN, RATE)?
209 }
210 DiffResult::Removed(ref r) => {
211 put_diff_pixels(y, &mut img, before_w as u32, r.data, RED, RATE)?
212 }
213 DiffResult::Common(ref c) => put_diff_pixels(y, &mut img, width, c.data, BLACK, 0.0)?,
214 }
215 }
216 Ok(ImageRgba8(img))
217}
218
219pub fn diff_slice(
220 before_slice: &[u8],
221 after_slice: &[u8],
222) -> Result<(Vec<u8>, u32, u32), DecodeError> {
223 let before_png = Reader::new(Cursor::new(before_slice))
224 .with_guessed_format()
225 .expect("Cursor io never fails")
226 .decode()
227 .expect("Unable to decode before_png");
228 let after_png = Reader::new(Cursor::new(after_slice))
229 .with_guessed_format()
230 .expect("Cursor io never fails")
231 .decode()
232 .expect("Unable to decode after_png");
233 diff(&before_png, &after_png).map(|img| {
234 (
235 img.as_bytes().to_vec(),
236 img.dimensions().0,
237 img.dimensions().1,
238 )
239 })
240}
241
242#[allow(dead_code)]
243fn gen_lcs<'a, T: PartialEq>(table: &Vec<Vec<u32>>, old: &[T], new: &'a [T]) -> Vec<&'a T> {
244 let o_len = old.len();
245 let n_len = new.len();
246 let mut o = 0;
247 let mut n = 0;
248 let mut res = vec![];
249 while o < o_len && n < n_len {
250 if old[o] == new[n] {
251 res.push(&new[n]);
252 o = o + 1;
253 n = n + 1; } else if table[n + 1][o] >= table[n][o + 1] {
255 n += 1; } else {
257 o += 1; }
259 }
260 res
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn should_create_table_with_encode_pixel_array() {
269 let old = [
270 255, 255, 255, 5, 167, 167, 133, 7, 133, 71, 132, 4, 255, 255, 255, 10,
271 ];
272 let old_chunks: Vec<String> = old.to_vec().chunks(4).map(encode).collect();
273 let new = [
274 255, 255, 255, 5, 133, 71, 132, 4, 167, 167, 133, 7, 255, 255, 255, 10,
275 ];
276 let new_chunks: Vec<String> = new.to_vec().chunks(4).map(encode).collect();
277 let lcs_table = create_table(&old_chunks, &new_chunks);
278 assert_eq!(
279 vec!["////BQ==", "p6eFBw==", "////Cg=="],
280 gen_lcs(&lcs_table, &old_chunks, &new_chunks)
281 );
282 assert_eq!(vec![255, 255, 255, 5], decode("////BQ==").unwrap());
283 assert_eq!(vec![167, 167, 133, 7], decode("p6eFBw==").unwrap());
284 assert_eq!(vec![255, 255, 255, 10], decode("////Cg==").unwrap());
285 assert_eq!(3, lcs_table[0][0]);
286 }
287
288 #[test]
289 fn should_create_table_with_strings2() {
290 let old = [
291 "HH", "ee", "ll", "ll", "oo", " ", "ww", "oo", "rr", "ll", "dd",
292 ];
293 let new = [
294 "HH", "aa", "cc", "kk", "yy", "ii", "nn", " ", "oo", "oo", "zz",
295 ];
296 let lcs_table = create_table(&old, &new);
297 let expected = vec![
298 vec![3, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
300 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
301 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
302 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
303 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
304 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
305 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
306 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
307 vec![2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0],
308 vec![1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
309 vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
310 vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
311 ];
312 assert_eq!(
313 ["HH", "oo", "oo"].iter().collect::<Vec<_>>(),
314 gen_lcs(&lcs_table, &old, &new)
315 );
316 assert_eq!(expected, lcs_table);
317 }
318
319 #[test]
320 fn should_create_table_with_strings() {
321 let old = ["H", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d"];
322 let new = ["H", "a", "c", "k", "y", "i", "n", " ", "o", "o", "z"];
323 let lcs_table = create_table(&old, &new);
324 let expected = vec![
325 vec![3, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
327 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
328 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
329 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
330 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
331 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
332 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
333 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
334 vec![2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0],
335 vec![1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
336 vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
337 vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
338 ];
339 assert_eq!(
340 ["H", "o", "o"].iter().collect::<Vec<_>>(),
341 gen_lcs(&lcs_table, &old, &new)
342 );
343 assert_eq!(expected, lcs_table);
344 }
345
346 #[test]
347 fn should_create_table_with_chars() {
348 let old = ['H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'];
349 let new = ['H', 'a', 'c', 'k', 'y', 'i', 'n', ' ', 'o', 'o', 'z'];
350 let lcs_table = create_table(&old, &new);
351 let expected = vec![
352 vec![3, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
354 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
355 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
356 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
357 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
358 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
359 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
360 vec![2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0],
361 vec![2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0],
362 vec![1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
363 vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
364 vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
365 ];
366 assert_eq!(
367 ['H', 'o', 'o'].iter().collect::<Vec<_>>(),
368 gen_lcs(&lcs_table, &old, &new)
369 );
370 assert_eq!(expected, lcs_table);
371 }
372
373 #[test]
374 fn should_create_table_with_numbers() {
375 let old = [1, 2, 3, 4];
376 let new = [2, 4];
377 let lcs_table = create_table(&old, &new);
378 let expected = vec![
379 vec![2, 2, 1, 1, 0],
380 vec![1, 1, 1, 1, 0],
381 vec![0, 0, 0, 0, 0],
382 ];
383 let res = gen_lcs(&lcs_table, &old, &new);
384 assert_eq!([2, 4].iter().collect::<Vec<_>>(), res);
385 assert_eq!(expected, lcs_table);
386 }
387}