1pub use falcon_ffmpeg::Frame;
5
6pub fn quarter_turns(a: f64, b: f64) -> u8 {
9 if a == 0.0 && b == 0.0 {
10 return 0;
11 }
12 let turns = (b.atan2(a) / std::f64::consts::FRAC_PI_2).round() as i64;
13 turns.rem_euclid(4) as u8
14}
15
16pub fn output_size(width: u32, height: u32, turns: u8, max_side: u32) -> (u32, u32) {
20 let (w, h) = if turns % 2 == 1 { (height, width) } else { (width, height) };
21 let long = w.max(h).max(1);
22 if long <= max_side {
23 return (w.max(1), h.max(1));
24 }
25 let scale = |v: u32| ((f64::from(v) * f64::from(max_side) / f64::from(long)).round() as u32).max(1);
26 (scale(w), scale(h))
27}
28
29pub fn from_bgra(src: &[u8], width: usize, height: usize, stride: usize, turns: u8) -> Frame {
33 let (w, h) = (width, height);
34 let (ow, oh) = if turns % 2 == 1 { (h, w) } else { (w, h) };
35 if w == 0 || h == 0 {
36 return Frame { width: ow, height: oh, rgba: Vec::new() };
37 }
38 let mut rgba = vec![0u8; ow * oh * 4];
39 if turns.is_multiple_of(4) {
40 for (y, out) in rgba.chunks_exact_mut(w * 4).enumerate() {
42 let row = src.get(y * stride..).unwrap_or(&[]);
43 for (d, px) in out.as_chunks_mut::<4>().0.iter_mut().enumerate() {
44 if let Some(s) = row.get(d * 4..d * 4 + 4) {
45 *px = [s[2], s[1], s[0], 255];
46 } else {
47 px[3] = 255;
48 }
49 }
50 }
51 return Frame { width: ow, height: oh, rgba };
52 }
53 for oy in 0..oh {
54 for ox in 0..ow {
55 let (sx, sy) = match turns % 4 {
57 1 => (oy, h - 1 - ox),
58 2 => (w - 1 - ox, h - 1 - oy),
59 3 => (w - 1 - oy, ox),
60 _ => (ox, oy),
61 };
62 let s = sy * stride + sx * 4;
63 let d = (oy * ow + ox) * 4;
64 if let Some(px) = src.get(s..s + 4) {
65 rgba[d] = px[2];
66 rgba[d + 1] = px[1];
67 rgba[d + 2] = px[0];
68 }
69 rgba[d + 3] = 255;
70 }
71 }
72 Frame { width: ow, height: oh, rgba }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 fn sample() -> Vec<u8> {
81 let mut v = Vec::new();
82 for y in 0..2u8 {
83 for x in 0..3u8 {
84 v.extend_from_slice(&[y * 3 + x, 0, 200, 0]);
85 }
86 v.extend_from_slice(&[9, 9, 9, 9]);
87 }
88 v
89 }
90
91 fn blues(f: &Frame) -> Vec<u8> {
92 f.rgba.as_chunks::<4>().0.iter().map(|p| p[2]).collect()
93 }
94
95 #[test]
96 fn bgra_rows_become_rgba() {
97 let f = from_bgra(&sample(), 3, 2, 16, 0);
98 assert_eq!((f.width, f.height), (3, 2));
99 assert_eq!(f.pixel(1, 1), [200, 0, 4, 255]);
100 assert_eq!(blues(&f), vec![0, 1, 2, 3, 4, 5]);
101 assert_eq!(f.mean(), [200.0, 0.0, 2.5]);
102 }
103
104 #[test]
105 fn turns_rotate_clockwise() {
106 let f = from_bgra(&sample(), 3, 2, 16, 1);
110 assert_eq!((f.width, f.height), (2, 3));
111 assert_eq!(blues(&f), vec![3, 0, 4, 1, 5, 2]);
112 assert_eq!(blues(&from_bgra(&sample(), 3, 2, 16, 2)), vec![5, 4, 3, 2, 1, 0]);
113 assert_eq!(blues(&from_bgra(&sample(), 3, 2, 16, 3)), vec![2, 5, 1, 4, 0, 3]);
114 }
115
116 #[test]
117 fn short_buffers_do_not_panic() {
118 let f = from_bgra(&sample()[..18], 3, 2, 16, 0);
119 assert_eq!(f.pixel(0, 1), [0, 0, 0, 255]);
120 assert_eq!(from_bgra(&[], 0, 0, 0, 1).rgba.len(), 0);
121 assert_eq!(from_bgra(&[], 0, 5, 0, 0).rgba.len(), 0);
122 }
123
124 #[test]
125 fn output_sizes_fit_the_long_side() {
126 assert_eq!(output_size(1920, 1080, 0, 640), (640, 360));
127 assert_eq!(output_size(1080, 1920, 0, 640), (360, 640));
128 assert_eq!(output_size(320, 180, 0, 640), (320, 180), "never enlarged");
129 assert_eq!(output_size(1080, 1920, 1, 640), (640, 360));
131 assert_eq!(output_size(4000, 2, 0, 64), (64, 1), "never empty");
132 assert_eq!(output_size(0, 0, 0, 64), (1, 1));
133 }
134
135 #[test]
136 fn transforms_map_to_turns() {
137 assert_eq!(quarter_turns(1.0, 0.0), 0);
138 assert_eq!(quarter_turns(0.0, 1.0), 1);
139 assert_eq!(quarter_turns(-1.0, 0.0), 2);
140 assert_eq!(quarter_turns(0.0, -1.0), 3);
141 assert_eq!(quarter_turns(0.0, 0.0), 0);
142 }
143}