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