1pub fn render_mono8(
14 width: usize,
15 height: usize,
16 background: u8,
17 blobs: &[(f64, f64)],
18 peak: f64,
19 sigma: f64,
20) -> Vec<u8> {
21 let mut buf = vec![background; width * height];
22 if peak <= 0.0 || sigma <= 0.0 {
23 return buf;
24 }
25 let two_sig2 = 2.0 * sigma * sigma;
26 let radius = (4.0 * sigma).ceil() as i64;
27 for &(cx, cy) in blobs {
28 let cxr = cx.round() as i64;
29 let cyr = cy.round() as i64;
30 let y0 = (cyr - radius).max(0);
31 let y1 = (cyr + radius + 1).min(height as i64);
32 let x0 = (cxr - radius).max(0);
33 let x1 = (cxr + radius + 1).min(width as i64);
34 for py in y0..y1 {
35 for px in x0..x1 {
36 let dx = px as f64 - cx;
37 let dy = py as f64 - cy;
38 let g = peak * (-(dx * dx + dy * dy) / two_sig2).exp();
39 let idx = py as usize * width + px as usize;
40 let v = (buf[idx] as f64 + g).round().clamp(0.0, 255.0) as u8;
41 buf[idx] = v;
42 }
43 }
44 }
45 buf
46}
47
48pub fn render_rgb8(
56 width: usize,
57 height: usize,
58 background: u8,
59 blobs: &[(f64, f64)],
60 peak: f64,
61 sigma: f64,
62) -> Vec<u8> {
63 let mono = render_mono8(width, height, background, blobs, peak, sigma);
64 let mut buf = vec![0u8; width * height * 3];
65 for (pixel, &v) in buf.chunks_exact_mut(3).zip(mono.iter()) {
66 pixel[0] = v;
67 pixel[1] = v;
68 pixel[2] = v;
69 }
70 buf
71}
72
73const NEUTRAL_CHROMA: u8 = 128;
75
76pub fn render_yuv422_uyvy(
84 width: usize,
85 height: usize,
86 background: u8,
87 blobs: &[(f64, f64)],
88 peak: f64,
89 sigma: f64,
90) -> Vec<u8> {
91 assert!(width.is_multiple_of(2), "YUV422 requires an even width");
92 let mono = render_mono8(width, height, background, blobs, peak, sigma);
93 let mut buf = vec![0u8; width * height * 2];
94 for (out_row, in_row) in buf
95 .chunks_exact_mut(width * 2)
96 .zip(mono.chunks_exact(width))
97 {
98 for (group, pair) in out_row.chunks_exact_mut(4).zip(in_row.chunks_exact(2)) {
99 group[0] = NEUTRAL_CHROMA; group[1] = pair[0]; group[2] = NEUTRAL_CHROMA; group[3] = pair[1]; }
104 }
105 buf
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn blob_is_brightest_at_center_and_background_far_away() {
114 let (w, h) = (64usize, 48usize);
115 let buf = render_mono8(w, h, 0, &[(20.0, 15.0)], 160.0, 1.5);
116 let at = |x: usize, y: usize| buf[y * w + x] as u32;
117 assert_eq!(at(20, 15), 160);
119 assert_eq!(at(60, 45), 0);
120 assert!(at(20, 15) > at(22, 15));
122 assert!(at(22, 15) > at(25, 15));
123 }
124
125 #[test]
126 fn empty_blobs_yield_flat_background() {
127 let buf = render_mono8(8, 8, 7, &[], 160.0, 1.5);
128 assert!(buf.iter().all(|&v| v == 7));
129 }
130
131 #[test]
132 fn yuv422_carries_mono_luma_with_neutral_chroma() {
133 let (w, h) = (64usize, 48usize);
134 let blobs = [(20.0, 15.0)];
135 let mono = render_mono8(w, h, 3, &blobs, 160.0, 1.5);
136 let yuv = render_yuv422_uyvy(w, h, 3, &blobs, 160.0, 1.5);
137 assert_eq!(yuv.len(), w * h * 2);
138 for (group, pair) in yuv.chunks_exact(4).zip(mono.chunks_exact(2)) {
140 assert_eq!(group[0], 128); assert_eq!(group[1], pair[0]); assert_eq!(group[2], 128); assert_eq!(group[3], pair[1]); }
145 }
146
147 #[test]
148 fn rgb8_replicates_mono_into_three_equal_channels() {
149 let (w, h) = (64usize, 48usize);
150 let blobs = [(20.0, 15.0)];
151 let mono = render_mono8(w, h, 3, &blobs, 160.0, 1.5);
152 let rgb = render_rgb8(w, h, 3, &blobs, 160.0, 1.5);
153 assert_eq!(rgb.len(), w * h * 3);
154 for (i, &v) in mono.iter().enumerate() {
155 assert_eq!(rgb[i * 3], v);
156 assert_eq!(rgb[i * 3 + 1], v);
157 assert_eq!(rgb[i * 3 + 2], v);
158 }
159 }
160}