rasterrocket_render/shading/
function.rs1use crate::pipe::Pattern;
17
18pub struct FunctionPattern {
24 func: Box<dyn Fn(f64, f64) -> [u8; 3] + Send + Sync>,
25}
26
27impl FunctionPattern {
28 #[must_use]
32 pub fn new<F>(func: F) -> Self
33 where
34 F: Fn(f64, f64) -> [u8; 3] + Send + Sync + 'static,
35 {
36 Self {
37 func: Box::new(func),
38 }
39 }
40}
41
42impl Pattern for FunctionPattern {
43 fn fill_span(&self, y: i32, x0: i32, x1: i32, out: &mut [u8]) {
44 let mut off = 0usize;
46 for x in x0..=x1 {
47 let rgb = (self.func)(f64::from(x), f64::from(y));
48 out[off] = rgb[0];
49 out[off + 1] = rgb[1];
50 out[off + 2] = rgb[2];
51 off += 3;
52 }
53 }
54
55 fn is_static_color(&self) -> bool {
56 false
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn encodes_x_coordinate_in_output() {
66 #[expect(
69 clippy::cast_possible_truncation,
70 clippy::cast_sign_loss,
71 reason = "test passes integer x ∈ [10,13]; cast is exact"
72 )]
73 let p = FunctionPattern::new(|x, _y| {
74 let v = x as u8;
75 [v, v, v]
76 });
77 let mut out = vec![0u8; 4 * 3];
78 p.fill_span(0, 10, 13, &mut out);
79 for i in 0..4usize {
80 #[expect(
81 clippy::cast_possible_truncation,
82 reason = "i ∈ [0,4); 10 + i ≤ 13 fits in u8"
83 )]
84 let expected = (10 + i) as u8;
85 assert_eq!(out[i * 3], expected, "pixel {i} R");
86 assert_eq!(out[i * 3 + 1], expected, "pixel {i} G");
87 assert_eq!(out[i * 3 + 2], expected, "pixel {i} B");
88 }
89 }
90
91 #[test]
92 fn constant_function_fills_uniform_color() {
93 let p = FunctionPattern::new(|_, _| [128, 64, 32]);
94 let mut out = vec![0u8; 3 * 3];
95 p.fill_span(5, 0, 2, &mut out);
96 for i in 0..3usize {
97 assert_eq!(out[i * 3], 128, "pixel {i} R");
98 assert_eq!(out[i * 3 + 1], 64, "pixel {i} G");
99 assert_eq!(out[i * 3 + 2], 32, "pixel {i} B");
100 }
101 }
102
103 #[test]
104 fn encodes_y_coordinate_in_output() {
105 #[expect(
106 clippy::cast_possible_truncation,
107 clippy::cast_sign_loss,
108 reason = "test passes integer y = 42; cast is exact"
109 )]
110 let p = FunctionPattern::new(|_x, y| [0, 0, y as u8]);
111 let mut out = [0u8; 3];
112 p.fill_span(42, 0, 0, &mut out);
113 assert_eq!(out[2], 42, "blue channel should encode y");
114 }
115
116 #[test]
117 fn single_pixel_span_writes_three_bytes() {
118 let p = FunctionPattern::new(|_, _| [1, 2, 3]);
119 let mut out = [0u8; 3];
120 p.fill_span(0, 7, 7, &mut out);
121 assert_eq!(out, [1, 2, 3]);
122 }
123}