1#[inline]
6pub fn scale_rgb(rgb: (u8, u8, u8), num: i32, den: i32) -> (u8, u8, u8) {
7 let channel = |v: u8| ((i32::from(v) * num) / den).clamp(0, 255) as u8;
8 (channel(rgb.0), channel(rgb.1), channel(rgb.2))
9}
10#[inline]
14pub fn lerp_rgb(a: (u8, u8, u8), b: (u8, u8, u8), num: u16, den: u16) -> (u8, u8, u8) {
15 let num = i32::from(num.min(den));
16 let den = den.max(1);
17 let channel =
18 |x: u8, y: u8| (i32::from(x) + (i32::from(y) - i32::from(x)) * num / i32::from(den)) as u8;
19 (channel(a.0, b.0), channel(a.1, b.1), channel(a.2, b.2))
20}
21#[inline]
24pub fn lerp_rgb_q8(a: (u8, u8, u8), b: (u8, u8, u8), t: u8) -> (u8, u8, u8) {
25 let channel =
26 |x: u8, y: u8| (i32::from(x) + (((i32::from(y) - i32::from(x)) * i32::from(t)) >> 8)) as u8;
27 (channel(a.0, b.0), channel(a.1, b.1), channel(a.2, b.2))
28}
29#[cfg(test)]
30mod tests {
31 use super::*;
32 #[test]
33 fn ratio_clamping_matches_legacy_formula_including_zero_denominator() {
34 for den in [0u16, 1, 2, 255, u16::MAX] {
35 for num in [0u16, 1, 9, 255, u16::MAX] {
36 for (a, b) in [((3, 4, 5), (9, 8, 7)), ((255, 0, 1), (0, 255, 0))] {
37 let (n, d) = (num.min(den) as i32, den.max(1) as i32);
38 let channel = |x: u8, y: u8| (x as i32 + (y as i32 - x as i32) * n / d) as u8;
39 assert_eq!(
40 lerp_rgb(a, b, num, den),
41 (channel(a.0, b.0), channel(a.1, b.1), channel(a.2, b.2))
42 );
43 }
44 }
45 }
46 }
47 #[test]
48 fn descending_rounding_is_explicit() {
49 assert_eq!(lerp_rgb((3, 0, 255), (0, 3, 0), 1, 2), (2, 1, 128));
50 assert_eq!(lerp_rgb_q8((3, 0, 255), (0, 3, 0), 128), (1, 1, 127));
51 assert_eq!(lerp_rgb((3, 4, 5), (9, 8, 7), 9, 0), (3, 4, 5));
52 assert_eq!(scale_rgb((1, 128, 255), 3, 2), (1, 192, 255));
53 assert_eq!(scale_rgb((1, 128, 255), -1, 2), (0, 0, 0));
54 }
55}