1use std::sync::Arc;
2
3use ad_core_rs::ndarray::{NDArray, NDDataBuffer};
4use ad_core_rs::ndarray_pool::NDArrayPool;
5use ad_core_rs::plugin::runtime::{NDPluginProcess, ProcessResult};
6
7#[derive(Debug, Clone)]
9pub enum OverlayShape {
10 Cross {
11 center_x: usize,
12 center_y: usize,
13 size: usize,
14 },
15 Rectangle {
16 x: usize,
17 y: usize,
18 width: usize,
19 height: usize,
20 },
21 Ellipse {
22 center_x: usize,
23 center_y: usize,
24 rx: usize,
25 ry: usize,
26 },
27 Text {
28 x: usize,
29 y: usize,
30 text: String,
31 font_size: usize,
32 },
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum DrawMode {
38 Set,
39 XOR,
40}
41
42#[derive(Debug, Clone)]
44pub struct OverlayDef {
45 pub shape: OverlayShape,
46 pub draw_mode: DrawMode,
47 pub color: [u8; 3], pub width_x: usize, pub width_y: usize, }
51
52const FONT_WIDTH: usize = 5;
57const FONT_HEIGHT: usize = 7;
58
59fn get_char_bitmap(ch: char) -> [[bool; FONT_WIDTH]; FONT_HEIGHT] {
62 let pattern: [u8; 7] = match ch {
63 ' ' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
64 '!' => [0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04],
65 '"' => [0x0A, 0x0A, 0x0A, 0x00, 0x00, 0x00, 0x00],
66 '#' => [0x0A, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x0A],
67 '$' => [0x04, 0x1E, 0x05, 0x0E, 0x14, 0x0F, 0x04],
68 '%' => [0x03, 0x13, 0x08, 0x04, 0x02, 0x19, 0x18],
69 '&' => [0x06, 0x09, 0x05, 0x02, 0x15, 0x09, 0x16],
70 '\'' => [0x04, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00],
71 '(' => [0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08],
72 ')' => [0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02],
73 '*' => [0x00, 0x04, 0x15, 0x0E, 0x15, 0x04, 0x00],
74 '+' => [0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00],
75 ',' => [0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x02],
76 '-' => [0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00],
77 '.' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04],
78 '/' => [0x10, 0x08, 0x08, 0x04, 0x02, 0x02, 0x01],
79 '0' => [0x0E, 0x11, 0x19, 0x15, 0x13, 0x11, 0x0E],
80 '1' => [0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x0E],
81 '2' => [0x0E, 0x11, 0x10, 0x08, 0x04, 0x02, 0x1F],
82 '3' => [0x0E, 0x11, 0x10, 0x0C, 0x10, 0x11, 0x0E],
83 '4' => [0x08, 0x0C, 0x0A, 0x09, 0x1F, 0x08, 0x08],
84 '5' => [0x1F, 0x01, 0x0F, 0x10, 0x10, 0x11, 0x0E],
85 '6' => [0x0C, 0x02, 0x01, 0x0F, 0x11, 0x11, 0x0E],
86 '7' => [0x1F, 0x10, 0x08, 0x04, 0x02, 0x02, 0x02],
87 '8' => [0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E],
88 '9' => [0x0E, 0x11, 0x11, 0x1E, 0x10, 0x08, 0x06],
89 ':' => [0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00],
90 ';' => [0x00, 0x00, 0x04, 0x00, 0x04, 0x04, 0x02],
91 '<' => [0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08],
92 '=' => [0x00, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x00],
93 '>' => [0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02],
94 '?' => [0x0E, 0x11, 0x10, 0x08, 0x04, 0x00, 0x04],
95 '@' => [0x0E, 0x11, 0x15, 0x1D, 0x05, 0x01, 0x0E],
96 'A' | 'a' => [0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11],
97 'B' | 'b' => [0x0F, 0x11, 0x11, 0x0F, 0x11, 0x11, 0x0F],
98 'C' | 'c' => [0x0E, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0E],
99 'D' | 'd' => [0x07, 0x09, 0x11, 0x11, 0x11, 0x09, 0x07],
100 'E' | 'e' => [0x1F, 0x01, 0x01, 0x0F, 0x01, 0x01, 0x1F],
101 'F' | 'f' => [0x1F, 0x01, 0x01, 0x0F, 0x01, 0x01, 0x01],
102 'G' | 'g' => [0x0E, 0x11, 0x01, 0x1D, 0x11, 0x11, 0x0E],
103 'H' | 'h' => [0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11],
104 'I' | 'i' => [0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E],
105 'J' | 'j' => [0x1C, 0x08, 0x08, 0x08, 0x08, 0x09, 0x06],
106 'K' | 'k' => [0x11, 0x09, 0x05, 0x03, 0x05, 0x09, 0x11],
107 'L' | 'l' => [0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1F],
108 'M' | 'm' => [0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11],
109 'N' | 'n' => [0x11, 0x13, 0x15, 0x15, 0x19, 0x11, 0x11],
110 'O' | 'o' => [0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E],
111 'P' | 'p' => [0x0F, 0x11, 0x11, 0x0F, 0x01, 0x01, 0x01],
112 'Q' | 'q' => [0x0E, 0x11, 0x11, 0x11, 0x15, 0x09, 0x16],
113 'R' | 'r' => [0x0F, 0x11, 0x11, 0x0F, 0x05, 0x09, 0x11],
114 'S' | 's' => [0x0E, 0x11, 0x01, 0x0E, 0x10, 0x11, 0x0E],
115 'T' | 't' => [0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04],
116 'U' | 'u' => [0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E],
117 'V' | 'v' => [0x11, 0x11, 0x11, 0x11, 0x0A, 0x0A, 0x04],
118 'W' | 'w' => [0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11],
119 'X' | 'x' => [0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11],
120 'Y' | 'y' => [0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04],
121 'Z' | 'z' => [0x1F, 0x10, 0x08, 0x04, 0x02, 0x01, 0x1F],
122 '[' => [0x0E, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0E],
123 '\\' => [0x01, 0x02, 0x02, 0x04, 0x08, 0x08, 0x10],
124 ']' => [0x0E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0E],
125 '^' => [0x04, 0x0A, 0x11, 0x00, 0x00, 0x00, 0x00],
126 '_' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F],
127 '`' => [0x02, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00],
128 '{' => [0x08, 0x04, 0x04, 0x02, 0x04, 0x04, 0x08],
129 '|' => [0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04],
130 '}' => [0x02, 0x04, 0x04, 0x08, 0x04, 0x04, 0x02],
131 '~' => [0x00, 0x00, 0x02, 0x15, 0x08, 0x00, 0x00],
132 _ => [0x00; 7], };
134
135 let mut bitmap = [[false; FONT_WIDTH]; FONT_HEIGHT];
136 for row in 0..FONT_HEIGHT {
137 let byte = pattern[row];
138 for col in 0..FONT_WIDTH {
139 bitmap[row][col] = (byte >> col) & 1 != 0;
140 }
141 }
142 bitmap
143}
144
145macro_rules! draw_on_typed_buffer {
150 ($data:expr, $T:ty, $overlays:expr, $w:expr, $h:expr, xor) => {{
151 draw_on_typed_buffer!(@inner $data, $T, $overlays, $w, $h, |data: &mut [$T], idx: usize, mode: DrawMode, value: $T| {
152 match mode {
153 DrawMode::Set => data[idx] = value,
154 DrawMode::XOR => data[idx] ^= value,
155 }
156 });
157 }};
158 ($data:expr, $T:ty, $overlays:expr, $w:expr, $h:expr, set_only) => {{
159 draw_on_typed_buffer!(@inner $data, $T, $overlays, $w, $h, |data: &mut [$T], idx: usize, _mode: DrawMode, value: $T| {
160 data[idx] = value;
161 });
162 }};
163 (@inner $data:expr, $T:ty, $overlays:expr, $w:expr, $h:expr, $set_fn:expr) => {{
164 let data: &mut [$T] = $data;
165 let w: usize = $w;
166 let h: usize = $h;
167 let set_fn = $set_fn;
168
169 for overlay in $overlays.iter() {
170 let value: $T = overlay.color[1] as $T;
172 let wx = overlay.width_x.max(1);
173 let wy = overlay.width_y.max(1);
174
175 let mut set_pixel = |x: usize, y: usize| {
177 if x < w && y < h {
178 let idx = y * w + x;
179 set_fn(data, idx, overlay.draw_mode, value);
180 }
181 };
182
183 match &overlay.shape {
184 OverlayShape::Cross { center_x, center_y, size } => {
185 let cx = *center_x;
186 let cy = *center_y;
187 let half = *size / 2;
188 let wy_half = wy / 2;
190 for dx in 0..=half.min(w) {
191 for t in 0..wy {
192 let ty = (cy + t).wrapping_sub(wy_half);
193 if cx + dx < w { set_pixel(cx + dx, ty); }
194 if dx <= cx { set_pixel(cx - dx, ty); }
195 }
196 }
197 let wx_half = wx / 2;
199 for dy in 0..=half.min(h) {
200 for t in 0..wx {
201 let tx = (cx + t).wrapping_sub(wx_half);
202 if cy + dy < h { set_pixel(tx, cy + dy); }
203 if dy <= cy { set_pixel(tx, cy - dy); }
204 }
205 }
206 }
207 OverlayShape::Rectangle { x, y, width, height } => {
208 let bx = wx.min(*width);
210 let by = wy.min(*height);
211 for dy in 0..by {
213 for dx in 0..*width {
214 set_pixel(x + dx, y + dy);
215 }
216 }
217 for dy in 0..by {
219 if *height > dy {
220 for dx in 0..*width {
221 set_pixel(x + dx, y + height - 1 - dy);
222 }
223 }
224 }
225 let inner_start = by;
227 let inner_end = height.saturating_sub(by);
228 for dy in inner_start..inner_end {
229 for t in 0..bx {
230 set_pixel(x + t, y + dy);
231 }
232 }
233 for dy in inner_start..inner_end {
235 for t in 0..bx {
236 if *width > t {
237 set_pixel(x + width - 1 - t, y + dy);
238 }
239 }
240 }
241 }
242 OverlayShape::Ellipse { center_x, center_y, rx, ry } => {
243 let cx = *center_x as f64;
244 let cy = *center_y as f64;
245 let rxf = *rx as f64;
246 let ryf = *ry as f64;
247 let wx_thickness = (wx as f64).min(rxf);
249 let wy_thickness = (wy as f64).min(ryf);
250 let steps = ((rxf + ryf) * 4.0).max(64.0) as usize;
251 for layer in 0..wx.max(wy) {
252 let frac = layer as f64;
253 let rx_cur = (rxf - frac * wx_thickness / wx.max(1) as f64).max(0.0);
254 let ry_cur = (ryf - frac * wy_thickness / wy.max(1) as f64).max(0.0);
255 if rx_cur <= 0.0 && ry_cur <= 0.0 {
256 break;
257 }
258 for i in 0..steps {
259 let angle = 2.0 * std::f64::consts::PI * i as f64 / steps as f64;
260 let px = (cx + rx_cur * angle.cos()).round() as usize;
261 let py = (cy + ry_cur * angle.sin()).round() as usize;
262 set_pixel(px, py);
263 }
264 }
265 }
266 OverlayShape::Text { x, y, text, font_size } => {
267 let scale = (*font_size).max(1) / FONT_HEIGHT.max(1);
268 let scale = scale.max(1);
269 let mut cursor_x = *x;
270 for ch in text.chars() {
271 let bitmap = get_char_bitmap(ch);
272 for row in 0..FONT_HEIGHT {
273 for col in 0..FONT_WIDTH {
274 if bitmap[row][col] {
275 for sy in 0..scale {
276 for sx in 0..scale {
277 set_pixel(
278 cursor_x + col * scale + sx,
279 *y + row * scale + sy,
280 );
281 }
282 }
283 }
284 }
285 }
286 cursor_x += (FONT_WIDTH + 1) * scale;
287 }
288 }
289 }
290 }
291 }};
292}
293
294pub fn draw_overlays(src: &NDArray, overlays: &[OverlayDef]) -> NDArray {
296 let mut arr = src.clone();
297 if arr.dims.len() < 2 {
298 return arr;
299 }
300 let w = arr.dims[0].size;
301 let h = arr.dims[1].size;
302
303 match &mut arr.data {
304 NDDataBuffer::U8(data) => {
305 draw_on_typed_buffer!(data.as_mut_slice(), u8, overlays, w, h, xor);
306 }
307 NDDataBuffer::U16(data) => {
308 draw_on_typed_buffer!(data.as_mut_slice(), u16, overlays, w, h, xor);
309 }
310 NDDataBuffer::I16(data) => {
311 draw_on_typed_buffer!(data.as_mut_slice(), i16, overlays, w, h, xor);
312 }
313 NDDataBuffer::I32(data) => {
314 draw_on_typed_buffer!(data.as_mut_slice(), i32, overlays, w, h, xor);
315 }
316 NDDataBuffer::U32(data) => {
317 draw_on_typed_buffer!(data.as_mut_slice(), u32, overlays, w, h, xor);
318 }
319 NDDataBuffer::F32(data) => {
320 draw_on_typed_buffer!(data.as_mut_slice(), f32, overlays, w, h, set_only);
321 }
322 NDDataBuffer::F64(data) => {
323 draw_on_typed_buffer!(data.as_mut_slice(), f64, overlays, w, h, set_only);
324 }
325 NDDataBuffer::I8(data) => {
326 draw_on_typed_buffer!(data.as_mut_slice(), i8, overlays, w, h, xor);
327 }
328 NDDataBuffer::I64(data) => {
329 draw_on_typed_buffer!(data.as_mut_slice(), i64, overlays, w, h, xor);
330 }
331 NDDataBuffer::U64(data) => {
332 draw_on_typed_buffer!(data.as_mut_slice(), u64, overlays, w, h, xor);
333 }
334 }
335
336 arr
337}
338
339const MAX_OVERLAYS: usize = 8;
341
342#[derive(Debug, Clone)]
344struct OverlaySlot {
345 use_overlay: bool,
346 shape: i32, draw_mode: i32, position_x: usize,
349 position_y: usize,
350 size_x: usize,
351 size_y: usize,
352 width_x: usize,
353 width_y: usize,
354 red: u8,
355 green: u8,
356 blue: u8,
357 display_text: String,
358 font: usize,
359}
360
361impl Default for OverlaySlot {
362 fn default() -> Self {
363 Self {
364 use_overlay: false,
365 shape: 1, draw_mode: 0,
367 position_x: 0,
368 position_y: 0,
369 size_x: 0,
370 size_y: 0,
371 width_x: 1,
372 width_y: 1,
373 red: 255,
374 green: 0,
375 blue: 0,
376 display_text: String::new(),
377 font: 0,
378 }
379 }
380}
381
382impl OverlaySlot {
383 fn to_overlay_def(&self) -> Option<OverlayDef> {
384 if !self.use_overlay {
385 return None;
386 }
387 let draw_mode = if self.draw_mode == 1 {
388 DrawMode::XOR
389 } else {
390 DrawMode::Set
391 };
392 let color = [self.red, self.green, self.blue];
393 let shape = match self.shape {
394 0 => OverlayShape::Cross {
395 center_x: self.position_x + self.size_x / 2,
396 center_y: self.position_y + self.size_y / 2,
397 size: self.size_x.max(self.size_y),
398 },
399 1 => OverlayShape::Rectangle {
400 x: self.position_x,
401 y: self.position_y,
402 width: self.size_x,
403 height: self.size_y,
404 },
405 2 => OverlayShape::Ellipse {
406 center_x: self.position_x + self.size_x / 2,
407 center_y: self.position_y + self.size_y / 2,
408 rx: self.size_x / 2,
409 ry: self.size_y / 2,
410 },
411 3 => OverlayShape::Text {
412 x: self.position_x,
413 y: self.position_y,
414 text: self.display_text.clone(),
415 font_size: (self.font + 1) * FONT_HEIGHT,
416 },
417 _ => OverlayShape::Rectangle {
418 x: self.position_x,
419 y: self.position_y,
420 width: self.size_x,
421 height: self.size_y,
422 },
423 };
424 Some(OverlayDef {
425 shape,
426 draw_mode,
427 color,
428 width_x: self.width_x,
429 width_y: self.width_y,
430 })
431 }
432}
433
434#[derive(Default)]
436struct OverlayParamIndices {
437 use_overlay: Option<usize>,
438 position_x: Option<usize>,
439 position_y: Option<usize>,
440 center_x: Option<usize>,
441 center_y: Option<usize>,
442 size_x: Option<usize>,
443 size_y: Option<usize>,
444 width_x: Option<usize>,
445 width_y: Option<usize>,
446 shape: Option<usize>,
447 draw_mode: Option<usize>,
448 red: Option<usize>,
449 green: Option<usize>,
450 blue: Option<usize>,
451 display_text: Option<usize>,
452 font: Option<usize>,
453}
454
455pub struct OverlayProcessor {
457 slots: [OverlaySlot; MAX_OVERLAYS],
458 params: OverlayParamIndices,
459}
460
461impl OverlayProcessor {
462 pub fn new(overlays: Vec<OverlayDef>) -> Self {
463 let mut slots: [OverlaySlot; MAX_OVERLAYS] = Default::default();
464 for (i, o) in overlays.into_iter().enumerate().take(MAX_OVERLAYS) {
465 let slot = &mut slots[i];
466 slot.use_overlay = true;
467 slot.draw_mode = if o.draw_mode == DrawMode::XOR { 1 } else { 0 };
468 slot.red = o.color[0];
469 slot.green = o.color[1];
470 slot.blue = o.color[2];
471 slot.width_x = o.width_x;
472 slot.width_y = o.width_y;
473 match o.shape {
474 OverlayShape::Cross {
475 center_x,
476 center_y,
477 size,
478 } => {
479 slot.shape = 0;
480 slot.position_x = center_x.saturating_sub(size / 2);
481 slot.position_y = center_y.saturating_sub(size / 2);
482 slot.size_x = size;
483 slot.size_y = size;
484 }
485 OverlayShape::Rectangle {
486 x,
487 y,
488 width,
489 height,
490 } => {
491 slot.shape = 1;
492 slot.position_x = x;
493 slot.position_y = y;
494 slot.size_x = width;
495 slot.size_y = height;
496 }
497 OverlayShape::Ellipse {
498 center_x,
499 center_y,
500 rx,
501 ry,
502 } => {
503 slot.shape = 2;
504 slot.position_x = center_x.saturating_sub(rx);
505 slot.position_y = center_y.saturating_sub(ry);
506 slot.size_x = rx * 2;
507 slot.size_y = ry * 2;
508 }
509 OverlayShape::Text {
510 x,
511 y,
512 text,
513 font_size,
514 } => {
515 slot.shape = 3;
516 slot.position_x = x;
517 slot.position_y = y;
518 slot.display_text = text;
519 slot.font = font_size / FONT_HEIGHT.max(1);
520 }
521 }
522 }
523 Self {
524 slots,
525 params: OverlayParamIndices::default(),
526 }
527 }
528
529 fn build_active_overlays(&self) -> Vec<OverlayDef> {
530 self.slots
531 .iter()
532 .filter_map(|s| s.to_overlay_def())
533 .collect()
534 }
535}
536
537impl NDPluginProcess for OverlayProcessor {
538 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
539 let active = self.build_active_overlays();
540 let out = draw_overlays(array, &active);
541 ProcessResult::arrays(vec![Arc::new(out)])
542 }
543
544 fn plugin_type(&self) -> &str {
545 "NDPluginOverlay"
546 }
547
548 fn register_params(
549 &mut self,
550 base: &mut asyn_rs::port::PortDriverBase,
551 ) -> asyn_rs::error::AsynResult<()> {
552 use asyn_rs::param::ParamType;
553 base.create_param("MAX_SIZE_X", ParamType::Int32)?;
554 base.create_param("MAX_SIZE_Y", ParamType::Int32)?;
555 base.create_param("NAME", ParamType::Octet)?;
556 base.create_param("USE", ParamType::Int32)?;
557 base.create_param("OVERLAY_POSITION_X", ParamType::Int32)?;
558 base.create_param("OVERLAY_POSITION_Y", ParamType::Int32)?;
559 base.create_param("OVERLAY_CENTER_X", ParamType::Int32)?;
560 base.create_param("OVERLAY_CENTER_Y", ParamType::Int32)?;
561 base.create_param("OVERLAY_SIZE_X", ParamType::Int32)?;
562 base.create_param("OVERLAY_SIZE_Y", ParamType::Int32)?;
563 base.create_param("OVERLAY_WIDTH_X", ParamType::Int32)?;
564 base.create_param("OVERLAY_WIDTH_Y", ParamType::Int32)?;
565 base.create_param("OVERLAY_SHAPE", ParamType::Int32)?;
566 base.create_param("OVERLAY_DRAW_MODE", ParamType::Int32)?;
567 base.create_param("OVERLAY_RED", ParamType::Int32)?;
568 base.create_param("OVERLAY_GREEN", ParamType::Int32)?;
569 base.create_param("OVERLAY_BLUE", ParamType::Int32)?;
570 base.create_param("OVERLAY_DISPLAY_TEXT", ParamType::Octet)?;
571 base.create_param("OVERLAY_TIMESTAMP_FORMAT", ParamType::Octet)?;
572 base.create_param("OVERLAY_FONT", ParamType::Int32)?;
573
574 self.params.use_overlay = base.find_param("USE");
575 self.params.position_x = base.find_param("OVERLAY_POSITION_X");
576 self.params.position_y = base.find_param("OVERLAY_POSITION_Y");
577 self.params.center_x = base.find_param("OVERLAY_CENTER_X");
578 self.params.center_y = base.find_param("OVERLAY_CENTER_Y");
579 self.params.size_x = base.find_param("OVERLAY_SIZE_X");
580 self.params.size_y = base.find_param("OVERLAY_SIZE_Y");
581 self.params.width_x = base.find_param("OVERLAY_WIDTH_X");
582 self.params.width_y = base.find_param("OVERLAY_WIDTH_Y");
583 self.params.shape = base.find_param("OVERLAY_SHAPE");
584 self.params.draw_mode = base.find_param("OVERLAY_DRAW_MODE");
585 self.params.red = base.find_param("OVERLAY_RED");
586 self.params.green = base.find_param("OVERLAY_GREEN");
587 self.params.blue = base.find_param("OVERLAY_BLUE");
588 self.params.display_text = base.find_param("OVERLAY_DISPLAY_TEXT");
589 self.params.font = base.find_param("OVERLAY_FONT");
590 Ok(())
591 }
592
593 fn on_param_change(
594 &mut self,
595 reason: usize,
596 params: &ad_core_rs::plugin::runtime::PluginParamSnapshot,
597 ) -> ad_core_rs::plugin::runtime::ParamChangeResult {
598 use ad_core_rs::plugin::runtime::{ParamChangeResult, ParamChangeValue, ParamUpdate};
599
600 let idx = params.addr as usize;
601 if idx >= MAX_OVERLAYS {
602 return ParamChangeResult::updates(vec![]);
603 }
604 let slot = &mut self.slots[idx];
605 let mut updates = Vec::new();
606
607 if Some(reason) == self.params.use_overlay {
608 slot.use_overlay = params.value.as_i32() != 0;
609 } else if Some(reason) == self.params.shape {
610 slot.shape = params.value.as_i32();
611 } else if Some(reason) == self.params.draw_mode {
612 slot.draw_mode = params.value.as_i32();
613 } else if Some(reason) == self.params.position_x {
614 slot.position_x = params.value.as_i32().max(0) as usize;
615 if let Some(ci) = self.params.center_x {
616 updates.push(ParamUpdate::int32_addr(
617 ci,
618 idx as i32,
619 (slot.position_x + slot.size_x / 2) as i32,
620 ));
621 }
622 } else if Some(reason) == self.params.position_y {
623 slot.position_y = params.value.as_i32().max(0) as usize;
624 if let Some(ci) = self.params.center_y {
625 updates.push(ParamUpdate::int32_addr(
626 ci,
627 idx as i32,
628 (slot.position_y + slot.size_y / 2) as i32,
629 ));
630 }
631 } else if Some(reason) == self.params.center_x {
632 let cx = params.value.as_i32().max(0) as usize;
633 slot.position_x = cx.saturating_sub(slot.size_x / 2);
634 if let Some(pi) = self.params.position_x {
635 updates.push(ParamUpdate::int32_addr(
636 pi,
637 idx as i32,
638 slot.position_x as i32,
639 ));
640 }
641 } else if Some(reason) == self.params.center_y {
642 let cy = params.value.as_i32().max(0) as usize;
643 slot.position_y = cy.saturating_sub(slot.size_y / 2);
644 if let Some(pi) = self.params.position_y {
645 updates.push(ParamUpdate::int32_addr(
646 pi,
647 idx as i32,
648 slot.position_y as i32,
649 ));
650 }
651 } else if Some(reason) == self.params.size_x {
652 slot.size_x = params.value.as_i32().max(0) as usize;
653 if let Some(ci) = self.params.center_x {
654 updates.push(ParamUpdate::int32_addr(
655 ci,
656 idx as i32,
657 (slot.position_x + slot.size_x / 2) as i32,
658 ));
659 }
660 } else if Some(reason) == self.params.size_y {
661 slot.size_y = params.value.as_i32().max(0) as usize;
662 if let Some(ci) = self.params.center_y {
663 updates.push(ParamUpdate::int32_addr(
664 ci,
665 idx as i32,
666 (slot.position_y + slot.size_y / 2) as i32,
667 ));
668 }
669 } else if Some(reason) == self.params.width_x {
670 slot.width_x = params.value.as_i32().max(0) as usize;
671 } else if Some(reason) == self.params.width_y {
672 slot.width_y = params.value.as_i32().max(0) as usize;
673 } else if Some(reason) == self.params.red {
674 slot.red = params.value.as_i32().clamp(0, 255) as u8;
675 } else if Some(reason) == self.params.green {
676 slot.green = params.value.as_i32().clamp(0, 255) as u8;
677 } else if Some(reason) == self.params.blue {
678 slot.blue = params.value.as_i32().clamp(0, 255) as u8;
679 } else if Some(reason) == self.params.display_text {
680 if let ParamChangeValue::Octet(s) = ¶ms.value {
681 slot.display_text = s.clone();
682 }
683 } else if Some(reason) == self.params.font {
684 slot.font = params.value.as_i32().max(0) as usize;
685 }
686
687 ParamChangeResult::updates(updates)
688 }
689}
690
691#[cfg(test)]
692mod tests {
693 use super::*;
694 use ad_core_rs::ndarray::{NDDataType, NDDimension};
695
696 fn make_8x8() -> NDArray {
697 NDArray::new(
698 vec![NDDimension::new(8), NDDimension::new(8)],
699 NDDataType::UInt8,
700 )
701 }
702
703 #[test]
704 fn test_rectangle() {
705 let arr = make_8x8();
706 let overlays = vec![OverlayDef {
707 shape: OverlayShape::Rectangle {
708 x: 1,
709 y: 1,
710 width: 4,
711 height: 3,
712 },
713 draw_mode: DrawMode::Set,
714 color: [0, 255, 0],
715 width_x: 1,
716 width_y: 1,
717 }];
718
719 let out = draw_overlays(&arr, &overlays);
720 if let NDDataBuffer::U8(ref v) = out.data {
721 assert_eq!(v[1 * 8 + 1], 255);
723 assert_eq!(v[1 * 8 + 2], 255);
724 assert_eq!(v[1 * 8 + 3], 255);
725 assert_eq!(v[1 * 8 + 4], 255);
726 assert_eq!(v[2 * 8 + 2], 0);
728 }
729 }
730
731 #[test]
732 fn test_xor_mode() {
733 let mut arr = make_8x8();
734 if let NDDataBuffer::U8(ref mut v) = arr.data {
735 v[0] = 0xFF;
736 }
737
738 let overlays = vec![OverlayDef {
739 shape: OverlayShape::Cross {
740 center_x: 0,
741 center_y: 0,
742 size: 2,
743 },
744 draw_mode: DrawMode::XOR,
745 color: [0, 0xFF, 0],
746 width_x: 1,
747 width_y: 1,
748 }];
749
750 let out = draw_overlays(&arr, &overlays);
751 if let NDDataBuffer::U8(ref v) = out.data {
752 assert_eq!(v[0], 0xFF);
755 assert_eq!(v[1], 0xFF);
757 assert_eq!(v[1 * 8], 0xFF);
759 }
760 }
761
762 #[test]
763 fn test_cross() {
764 let arr = make_8x8();
765 let overlays = vec![OverlayDef {
766 shape: OverlayShape::Cross {
767 center_x: 4,
768 center_y: 4,
769 size: 4,
770 },
771 draw_mode: DrawMode::Set,
772 color: [0, 200, 0],
773 width_x: 1,
774 width_y: 1,
775 }];
776
777 let out = draw_overlays(&arr, &overlays);
778 if let NDDataBuffer::U8(ref v) = out.data {
779 assert_eq!(v[4 * 8 + 4], 200); assert_eq!(v[4 * 8 + 6], 200); assert_eq!(v[6 * 8 + 4], 200); }
783 }
784
785 #[test]
786 fn test_text_rendering() {
787 let arr = NDArray::new(
789 vec![NDDimension::new(20), NDDimension::new(10)],
790 NDDataType::UInt8,
791 );
792 let overlays = vec![OverlayDef {
793 shape: OverlayShape::Text {
794 x: 0,
795 y: 0,
796 text: "Hi".to_string(),
797 font_size: 7,
798 },
799 draw_mode: DrawMode::Set,
800 color: [0, 255, 0],
801 width_x: 1,
802 width_y: 1,
803 }];
804
805 let out = draw_overlays(&arr, &overlays);
806 if let NDDataBuffer::U8(ref v) = out.data {
807 let w = 20;
808 assert_eq!(v[0 * w + 0], 255);
811 assert_eq!(v[0 * w + 4], 255);
813 assert_eq!(v[0 * w + 2], 0);
815
816 assert_eq!(v[0 * w + 6 + 1], 255);
819 assert_eq!(v[0 * w + 6 + 2], 255);
820 assert_eq!(v[0 * w + 6 + 3], 255);
821 }
822 }
823
824 #[test]
825 fn test_u16_overlay() {
826 let arr = NDArray::new(
827 vec![NDDimension::new(8), NDDimension::new(8)],
828 NDDataType::UInt16,
829 );
830 let overlays = vec![OverlayDef {
832 shape: OverlayShape::Rectangle {
833 x: 1,
834 y: 1,
835 width: 4,
836 height: 3,
837 },
838 draw_mode: DrawMode::Set,
839 color: [0, 200, 0],
840 width_x: 1,
841 width_y: 1,
842 }];
843
844 let out = draw_overlays(&arr, &overlays);
845 if let NDDataBuffer::U16(ref v) = out.data {
846 assert_eq!(v[1 * 8 + 1], 200);
848 assert_eq!(v[1 * 8 + 4], 200);
849 assert_eq!(v[2 * 8 + 2], 0);
851 }
852 }
853
854 #[test]
855 fn test_f32_overlay_ignores_xor() {
856 let arr = NDArray::new(
857 vec![NDDimension::new(8), NDDimension::new(8)],
858 NDDataType::Float32,
859 );
860 let overlays = vec![OverlayDef {
861 shape: OverlayShape::Cross {
862 center_x: 4,
863 center_y: 4,
864 size: 2,
865 },
866 draw_mode: DrawMode::XOR, color: [0, 100, 0],
868 width_x: 1,
869 width_y: 1,
870 }];
871
872 let out = draw_overlays(&arr, &overlays);
873 if let NDDataBuffer::F32(ref v) = out.data {
874 assert_eq!(v[4 * 8 + 4], 100.0);
876 }
877 }
878}