1use std::sync::Arc;
2
3use ad_core::ndarray::{NDArray, NDDataBuffer};
4use ad_core::ndarray_pool::NDArrayPool;
5use ad_core::plugin::runtime::{NDPluginProcess, ProcessResult};
6
7#[derive(Debug, Clone)]
9pub enum OverlayShape {
10 Cross { center_x: usize, center_y: usize, size: usize },
11 Rectangle { x: usize, y: usize, width: usize, height: usize },
12 Ellipse { center_x: usize, center_y: usize, rx: usize, ry: usize },
13 Text { x: usize, y: usize, text: String, font_size: usize },
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum DrawMode {
19 Set,
20 XOR,
21}
22
23#[derive(Debug, Clone)]
25pub struct OverlayDef {
26 pub shape: OverlayShape,
27 pub draw_mode: DrawMode,
28 pub color: [u8; 3], }
30
31const FONT_WIDTH: usize = 5;
36const FONT_HEIGHT: usize = 7;
37
38fn get_char_bitmap(ch: char) -> [[bool; FONT_WIDTH]; FONT_HEIGHT] {
41 let pattern: [u8; 7] = match ch {
42 ' ' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
43 '!' => [0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04],
44 '"' => [0x0A, 0x0A, 0x0A, 0x00, 0x00, 0x00, 0x00],
45 '#' => [0x0A, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x0A],
46 '$' => [0x04, 0x1E, 0x05, 0x0E, 0x14, 0x0F, 0x04],
47 '%' => [0x03, 0x13, 0x08, 0x04, 0x02, 0x19, 0x18],
48 '&' => [0x06, 0x09, 0x05, 0x02, 0x15, 0x09, 0x16],
49 '\'' => [0x04, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00],
50 '(' => [0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08],
51 ')' => [0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02],
52 '*' => [0x00, 0x04, 0x15, 0x0E, 0x15, 0x04, 0x00],
53 '+' => [0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00],
54 ',' => [0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x02],
55 '-' => [0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00],
56 '.' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04],
57 '/' => [0x10, 0x08, 0x08, 0x04, 0x02, 0x02, 0x01],
58 '0' => [0x0E, 0x11, 0x19, 0x15, 0x13, 0x11, 0x0E],
59 '1' => [0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x0E],
60 '2' => [0x0E, 0x11, 0x10, 0x08, 0x04, 0x02, 0x1F],
61 '3' => [0x0E, 0x11, 0x10, 0x0C, 0x10, 0x11, 0x0E],
62 '4' => [0x08, 0x0C, 0x0A, 0x09, 0x1F, 0x08, 0x08],
63 '5' => [0x1F, 0x01, 0x0F, 0x10, 0x10, 0x11, 0x0E],
64 '6' => [0x0C, 0x02, 0x01, 0x0F, 0x11, 0x11, 0x0E],
65 '7' => [0x1F, 0x10, 0x08, 0x04, 0x02, 0x02, 0x02],
66 '8' => [0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E],
67 '9' => [0x0E, 0x11, 0x11, 0x1E, 0x10, 0x08, 0x06],
68 ':' => [0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00],
69 ';' => [0x00, 0x00, 0x04, 0x00, 0x04, 0x04, 0x02],
70 '<' => [0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08],
71 '=' => [0x00, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x00],
72 '>' => [0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02],
73 '?' => [0x0E, 0x11, 0x10, 0x08, 0x04, 0x00, 0x04],
74 '@' => [0x0E, 0x11, 0x15, 0x1D, 0x05, 0x01, 0x0E],
75 'A' | 'a' => [0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11],
76 'B' | 'b' => [0x0F, 0x11, 0x11, 0x0F, 0x11, 0x11, 0x0F],
77 'C' | 'c' => [0x0E, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0E],
78 'D' | 'd' => [0x07, 0x09, 0x11, 0x11, 0x11, 0x09, 0x07],
79 'E' | 'e' => [0x1F, 0x01, 0x01, 0x0F, 0x01, 0x01, 0x1F],
80 'F' | 'f' => [0x1F, 0x01, 0x01, 0x0F, 0x01, 0x01, 0x01],
81 'G' | 'g' => [0x0E, 0x11, 0x01, 0x1D, 0x11, 0x11, 0x0E],
82 'H' | 'h' => [0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11],
83 'I' | 'i' => [0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E],
84 'J' | 'j' => [0x1C, 0x08, 0x08, 0x08, 0x08, 0x09, 0x06],
85 'K' | 'k' => [0x11, 0x09, 0x05, 0x03, 0x05, 0x09, 0x11],
86 'L' | 'l' => [0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1F],
87 'M' | 'm' => [0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11],
88 'N' | 'n' => [0x11, 0x13, 0x15, 0x15, 0x19, 0x11, 0x11],
89 'O' | 'o' => [0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E],
90 'P' | 'p' => [0x0F, 0x11, 0x11, 0x0F, 0x01, 0x01, 0x01],
91 'Q' | 'q' => [0x0E, 0x11, 0x11, 0x11, 0x15, 0x09, 0x16],
92 'R' | 'r' => [0x0F, 0x11, 0x11, 0x0F, 0x05, 0x09, 0x11],
93 'S' | 's' => [0x0E, 0x11, 0x01, 0x0E, 0x10, 0x11, 0x0E],
94 'T' | 't' => [0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04],
95 'U' | 'u' => [0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E],
96 'V' | 'v' => [0x11, 0x11, 0x11, 0x11, 0x0A, 0x0A, 0x04],
97 'W' | 'w' => [0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11],
98 'X' | 'x' => [0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11],
99 'Y' | 'y' => [0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04],
100 'Z' | 'z' => [0x1F, 0x10, 0x08, 0x04, 0x02, 0x01, 0x1F],
101 '[' => [0x0E, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0E],
102 '\\' => [0x01, 0x02, 0x02, 0x04, 0x08, 0x08, 0x10],
103 ']' => [0x0E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0E],
104 '^' => [0x04, 0x0A, 0x11, 0x00, 0x00, 0x00, 0x00],
105 '_' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F],
106 '`' => [0x02, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00],
107 '{' => [0x08, 0x04, 0x04, 0x02, 0x04, 0x04, 0x08],
108 '|' => [0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04],
109 '}' => [0x02, 0x04, 0x04, 0x08, 0x04, 0x04, 0x02],
110 '~' => [0x00, 0x00, 0x02, 0x15, 0x08, 0x00, 0x00],
111 _ => [0x00; 7], };
113
114 let mut bitmap = [[false; FONT_WIDTH]; FONT_HEIGHT];
115 for row in 0..FONT_HEIGHT {
116 let byte = pattern[row];
117 for col in 0..FONT_WIDTH {
118 bitmap[row][col] = (byte >> col) & 1 != 0;
119 }
120 }
121 bitmap
122}
123
124macro_rules! draw_on_typed_buffer {
129 ($data:expr, $T:ty, $overlays:expr, $w:expr, $h:expr, xor) => {{
130 draw_on_typed_buffer!(@inner $data, $T, $overlays, $w, $h, |data: &mut [$T], idx: usize, mode: DrawMode, value: $T| {
131 match mode {
132 DrawMode::Set => data[idx] = value,
133 DrawMode::XOR => data[idx] ^= value,
134 }
135 });
136 }};
137 ($data:expr, $T:ty, $overlays:expr, $w:expr, $h:expr, set_only) => {{
138 draw_on_typed_buffer!(@inner $data, $T, $overlays, $w, $h, |data: &mut [$T], idx: usize, _mode: DrawMode, value: $T| {
139 data[idx] = value;
140 });
141 }};
142 (@inner $data:expr, $T:ty, $overlays:expr, $w:expr, $h:expr, $set_fn:expr) => {{
143 let data: &mut [$T] = $data;
144 let w: usize = $w;
145 let h: usize = $h;
146 let set_fn = $set_fn;
147
148 for overlay in $overlays.iter() {
149 let value: $T = overlay.color[0] as $T;
150
151 let mut set_pixel = |x: usize, y: usize| {
153 if x < w && y < h {
154 let idx = y * w + x;
155 set_fn(data, idx, overlay.draw_mode, value);
156 }
157 };
158
159 match &overlay.shape {
160 OverlayShape::Cross { center_x, center_y, size } => {
161 let cx = *center_x;
162 let cy = *center_y;
163 let half = *size / 2;
164 for dx in 0..=half.min(w) {
165 if cx + dx < w { set_pixel(cx + dx, cy); }
166 if dx <= cx { set_pixel(cx - dx, cy); }
167 }
168 for dy in 0..=half.min(h) {
169 if cy + dy < h { set_pixel(cx, cy + dy); }
170 if dy <= cy { set_pixel(cx, cy - dy); }
171 }
172 }
173 OverlayShape::Rectangle { x, y, width, height } => {
174 for dx in 0..*width {
175 set_pixel(x + dx, *y);
176 if *y + height > 0 {
177 set_pixel(x + dx, y + height - 1);
178 }
179 }
180 for dy in 0..*height {
181 set_pixel(*x, y + dy);
182 if *x + width > 0 {
183 set_pixel(x + width - 1, y + dy);
184 }
185 }
186 }
187 OverlayShape::Ellipse { center_x, center_y, rx, ry } => {
188 let cx = *center_x as f64;
189 let cy = *center_y as f64;
190 let rxf = *rx as f64;
191 let ryf = *ry as f64;
192 let steps = ((rxf + ryf) * 4.0) as usize;
193 for i in 0..steps {
194 let angle = 2.0 * std::f64::consts::PI * i as f64 / steps as f64;
195 let px = (cx + rxf * angle.cos()).round() as usize;
196 let py = (cy + ryf * angle.sin()).round() as usize;
197 set_pixel(px, py);
198 }
199 }
200 OverlayShape::Text { x, y, text, font_size } => {
201 let scale = (*font_size).max(1) / FONT_HEIGHT.max(1);
202 let scale = scale.max(1);
203 let mut cursor_x = *x;
204 for ch in text.chars() {
205 let bitmap = get_char_bitmap(ch);
206 for row in 0..FONT_HEIGHT {
207 for col in 0..FONT_WIDTH {
208 if bitmap[row][col] {
209 for sy in 0..scale {
210 for sx in 0..scale {
211 set_pixel(
212 cursor_x + col * scale + sx,
213 *y + row * scale + sy,
214 );
215 }
216 }
217 }
218 }
219 }
220 cursor_x += (FONT_WIDTH + 1) * scale;
221 }
222 }
223 }
224 }
225 }};
226}
227
228pub fn draw_overlays(src: &NDArray, overlays: &[OverlayDef]) -> NDArray {
230 let mut arr = src.clone();
231 if arr.dims.len() < 2 {
232 return arr;
233 }
234 let w = arr.dims[0].size;
235 let h = arr.dims[1].size;
236
237 match &mut arr.data {
238 NDDataBuffer::U8(ref mut data) => {
239 draw_on_typed_buffer!(data.as_mut_slice(), u8, overlays, w, h, xor);
240 }
241 NDDataBuffer::U16(ref mut data) => {
242 draw_on_typed_buffer!(data.as_mut_slice(), u16, overlays, w, h, xor);
243 }
244 NDDataBuffer::I16(ref mut data) => {
245 draw_on_typed_buffer!(data.as_mut_slice(), i16, overlays, w, h, xor);
246 }
247 NDDataBuffer::I32(ref mut data) => {
248 draw_on_typed_buffer!(data.as_mut_slice(), i32, overlays, w, h, xor);
249 }
250 NDDataBuffer::U32(ref mut data) => {
251 draw_on_typed_buffer!(data.as_mut_slice(), u32, overlays, w, h, xor);
252 }
253 NDDataBuffer::F32(ref mut data) => {
254 draw_on_typed_buffer!(data.as_mut_slice(), f32, overlays, w, h, set_only);
255 }
256 NDDataBuffer::F64(ref mut data) => {
257 draw_on_typed_buffer!(data.as_mut_slice(), f64, overlays, w, h, set_only);
258 }
259 _ => {} }
261
262 arr
263}
264
265pub struct OverlayProcessor {
267 overlays: Vec<OverlayDef>,
268}
269
270impl OverlayProcessor {
271 pub fn new(overlays: Vec<OverlayDef>) -> Self {
272 Self { overlays }
273 }
274}
275
276impl NDPluginProcess for OverlayProcessor {
277 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
278 let out = draw_overlays(array, &self.overlays);
279 ProcessResult::arrays(vec![Arc::new(out)])
280 }
281
282 fn plugin_type(&self) -> &str {
283 "NDPluginOverlay"
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290 use ad_core::ndarray::{NDDataType, NDDimension};
291
292 fn make_8x8() -> NDArray {
293 NDArray::new(
294 vec![NDDimension::new(8), NDDimension::new(8)],
295 NDDataType::UInt8,
296 )
297 }
298
299 #[test]
300 fn test_rectangle() {
301 let arr = make_8x8();
302 let overlays = vec![OverlayDef {
303 shape: OverlayShape::Rectangle { x: 1, y: 1, width: 4, height: 3 },
304 draw_mode: DrawMode::Set,
305 color: [255, 0, 0],
306 }];
307
308 let out = draw_overlays(&arr, &overlays);
309 if let NDDataBuffer::U8(ref v) = out.data {
310 assert_eq!(v[1 * 8 + 1], 255);
312 assert_eq!(v[1 * 8 + 2], 255);
313 assert_eq!(v[1 * 8 + 3], 255);
314 assert_eq!(v[1 * 8 + 4], 255);
315 assert_eq!(v[2 * 8 + 2], 0);
317 }
318 }
319
320 #[test]
321 fn test_xor_mode() {
322 let mut arr = make_8x8();
323 if let NDDataBuffer::U8(ref mut v) = arr.data {
324 v[0] = 0xFF;
325 }
326
327 let overlays = vec![OverlayDef {
328 shape: OverlayShape::Cross { center_x: 0, center_y: 0, size: 2 },
329 draw_mode: DrawMode::XOR,
330 color: [0xFF, 0, 0],
331 }];
332
333 let out = draw_overlays(&arr, &overlays);
334 if let NDDataBuffer::U8(ref v) = out.data {
335 assert_eq!(v[0], 0xFF);
338 assert_eq!(v[1], 0xFF);
340 assert_eq!(v[1 * 8], 0xFF);
342 }
343 }
344
345 #[test]
346 fn test_cross() {
347 let arr = make_8x8();
348 let overlays = vec![OverlayDef {
349 shape: OverlayShape::Cross { center_x: 4, center_y: 4, size: 4 },
350 draw_mode: DrawMode::Set,
351 color: [200, 0, 0],
352 }];
353
354 let out = draw_overlays(&arr, &overlays);
355 if let NDDataBuffer::U8(ref v) = out.data {
356 assert_eq!(v[4 * 8 + 4], 200); assert_eq!(v[4 * 8 + 6], 200); assert_eq!(v[6 * 8 + 4], 200); }
360 }
361
362 #[test]
363 fn test_text_rendering() {
364 let arr = NDArray::new(
366 vec![NDDimension::new(20), NDDimension::new(10)],
367 NDDataType::UInt8,
368 );
369 let overlays = vec![OverlayDef {
370 shape: OverlayShape::Text {
371 x: 0,
372 y: 0,
373 text: "Hi".to_string(),
374 font_size: 7,
375 },
376 draw_mode: DrawMode::Set,
377 color: [255, 0, 0],
378 }];
379
380 let out = draw_overlays(&arr, &overlays);
381 if let NDDataBuffer::U8(ref v) = out.data {
382 let w = 20;
383 assert_eq!(v[0 * w + 0], 255);
386 assert_eq!(v[0 * w + 4], 255);
388 assert_eq!(v[0 * w + 2], 0);
390
391 assert_eq!(v[0 * w + 6 + 1], 255);
394 assert_eq!(v[0 * w + 6 + 2], 255);
395 assert_eq!(v[0 * w + 6 + 3], 255);
396 }
397 }
398
399 #[test]
400 fn test_u16_overlay() {
401 let arr = NDArray::new(
402 vec![NDDimension::new(8), NDDimension::new(8)],
403 NDDataType::UInt16,
404 );
405 let overlays = vec![OverlayDef {
407 shape: OverlayShape::Rectangle { x: 1, y: 1, width: 4, height: 3 },
408 draw_mode: DrawMode::Set,
409 color: [200, 0, 0],
410 }];
411
412 let out = draw_overlays(&arr, &overlays);
413 if let NDDataBuffer::U16(ref v) = out.data {
414 assert_eq!(v[1 * 8 + 1], 200);
416 assert_eq!(v[1 * 8 + 4], 200);
417 assert_eq!(v[2 * 8 + 2], 0);
419 }
420 }
421
422 #[test]
423 fn test_f32_overlay_ignores_xor() {
424 let arr = NDArray::new(
425 vec![NDDimension::new(8), NDDimension::new(8)],
426 NDDataType::Float32,
427 );
428 let overlays = vec![OverlayDef {
429 shape: OverlayShape::Cross { center_x: 4, center_y: 4, size: 2 },
430 draw_mode: DrawMode::XOR, color: [100, 0, 0],
432 }];
433
434 let out = draw_overlays(&arr, &overlays);
435 if let NDDataBuffer::F32(ref v) = out.data {
436 assert_eq!(v[4 * 8 + 4], 100.0);
438 }
439 }
440}