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 {
549 updates.push(ParamUpdate::int32_addr(
550 ci,
551 idx as i32,
552 (slot.position_x + slot.size_x / 2) as i32,
553 ));
554 }
555 } else if Some(reason) == self.params.position_y {
556 slot.position_y = params.value.as_i32().max(0) as usize;
557 if let Some(ci) = self.params.center_y {
558 updates.push(ParamUpdate::int32_addr(
559 ci,
560 idx as i32,
561 (slot.position_y + slot.size_y / 2) as i32,
562 ));
563 }
564 } else if Some(reason) == self.params.center_x {
565 let cx = params.value.as_i32().max(0) as usize;
566 slot.position_x = cx.saturating_sub(slot.size_x / 2);
567 if let Some(pi) = self.params.position_x {
568 updates.push(ParamUpdate::int32_addr(
569 pi,
570 idx as i32,
571 slot.position_x as i32,
572 ));
573 }
574 } else if Some(reason) == self.params.center_y {
575 let cy = params.value.as_i32().max(0) as usize;
576 slot.position_y = cy.saturating_sub(slot.size_y / 2);
577 if let Some(pi) = self.params.position_y {
578 updates.push(ParamUpdate::int32_addr(
579 pi,
580 idx as i32,
581 slot.position_y as i32,
582 ));
583 }
584 } else if Some(reason) == self.params.size_x {
585 slot.size_x = params.value.as_i32().max(0) as usize;
586 if let Some(ci) = self.params.center_x {
587 updates.push(ParamUpdate::int32_addr(
588 ci,
589 idx as i32,
590 (slot.position_x + slot.size_x / 2) as i32,
591 ));
592 }
593 } else if Some(reason) == self.params.size_y {
594 slot.size_y = params.value.as_i32().max(0) as usize;
595 if let Some(ci) = self.params.center_y {
596 updates.push(ParamUpdate::int32_addr(
597 ci,
598 idx as i32,
599 (slot.position_y + slot.size_y / 2) as i32,
600 ));
601 }
602 } else if Some(reason) == self.params.red {
603 slot.red = params.value.as_i32().clamp(0, 255) as u8;
604 } else if Some(reason) == self.params.green {
605 slot.green = params.value.as_i32().clamp(0, 255) as u8;
606 } else if Some(reason) == self.params.blue {
607 slot.blue = params.value.as_i32().clamp(0, 255) as u8;
608 } else if Some(reason) == self.params.display_text {
609 if let ParamChangeValue::Octet(s) = ¶ms.value {
610 slot.display_text = s.clone();
611 }
612 } else if Some(reason) == self.params.font {
613 slot.font = params.value.as_i32().max(0) as usize;
614 }
615
616 ParamChangeResult::updates(updates)
617 }
618}
619
620#[cfg(test)]
621mod tests {
622 use super::*;
623 use ad_core_rs::ndarray::{NDDataType, NDDimension};
624
625 fn make_8x8() -> NDArray {
626 NDArray::new(
627 vec![NDDimension::new(8), NDDimension::new(8)],
628 NDDataType::UInt8,
629 )
630 }
631
632 #[test]
633 fn test_rectangle() {
634 let arr = make_8x8();
635 let overlays = vec![OverlayDef {
636 shape: OverlayShape::Rectangle {
637 x: 1,
638 y: 1,
639 width: 4,
640 height: 3,
641 },
642 draw_mode: DrawMode::Set,
643 color: [255, 0, 0],
644 }];
645
646 let out = draw_overlays(&arr, &overlays);
647 if let NDDataBuffer::U8(ref v) = out.data {
648 assert_eq!(v[1 * 8 + 1], 255);
650 assert_eq!(v[1 * 8 + 2], 255);
651 assert_eq!(v[1 * 8 + 3], 255);
652 assert_eq!(v[1 * 8 + 4], 255);
653 assert_eq!(v[2 * 8 + 2], 0);
655 }
656 }
657
658 #[test]
659 fn test_xor_mode() {
660 let mut arr = make_8x8();
661 if let NDDataBuffer::U8(ref mut v) = arr.data {
662 v[0] = 0xFF;
663 }
664
665 let overlays = vec![OverlayDef {
666 shape: OverlayShape::Cross {
667 center_x: 0,
668 center_y: 0,
669 size: 2,
670 },
671 draw_mode: DrawMode::XOR,
672 color: [0xFF, 0, 0],
673 }];
674
675 let out = draw_overlays(&arr, &overlays);
676 if let NDDataBuffer::U8(ref v) = out.data {
677 assert_eq!(v[0], 0xFF);
680 assert_eq!(v[1], 0xFF);
682 assert_eq!(v[1 * 8], 0xFF);
684 }
685 }
686
687 #[test]
688 fn test_cross() {
689 let arr = make_8x8();
690 let overlays = vec![OverlayDef {
691 shape: OverlayShape::Cross {
692 center_x: 4,
693 center_y: 4,
694 size: 4,
695 },
696 draw_mode: DrawMode::Set,
697 color: [200, 0, 0],
698 }];
699
700 let out = draw_overlays(&arr, &overlays);
701 if let NDDataBuffer::U8(ref v) = out.data {
702 assert_eq!(v[4 * 8 + 4], 200); assert_eq!(v[4 * 8 + 6], 200); assert_eq!(v[6 * 8 + 4], 200); }
706 }
707
708 #[test]
709 fn test_text_rendering() {
710 let arr = NDArray::new(
712 vec![NDDimension::new(20), NDDimension::new(10)],
713 NDDataType::UInt8,
714 );
715 let overlays = vec![OverlayDef {
716 shape: OverlayShape::Text {
717 x: 0,
718 y: 0,
719 text: "Hi".to_string(),
720 font_size: 7,
721 },
722 draw_mode: DrawMode::Set,
723 color: [255, 0, 0],
724 }];
725
726 let out = draw_overlays(&arr, &overlays);
727 if let NDDataBuffer::U8(ref v) = out.data {
728 let w = 20;
729 assert_eq!(v[0 * w + 0], 255);
732 assert_eq!(v[0 * w + 4], 255);
734 assert_eq!(v[0 * w + 2], 0);
736
737 assert_eq!(v[0 * w + 6 + 1], 255);
740 assert_eq!(v[0 * w + 6 + 2], 255);
741 assert_eq!(v[0 * w + 6 + 3], 255);
742 }
743 }
744
745 #[test]
746 fn test_u16_overlay() {
747 let arr = NDArray::new(
748 vec![NDDimension::new(8), NDDimension::new(8)],
749 NDDataType::UInt16,
750 );
751 let overlays = vec![OverlayDef {
753 shape: OverlayShape::Rectangle {
754 x: 1,
755 y: 1,
756 width: 4,
757 height: 3,
758 },
759 draw_mode: DrawMode::Set,
760 color: [200, 0, 0],
761 }];
762
763 let out = draw_overlays(&arr, &overlays);
764 if let NDDataBuffer::U16(ref v) = out.data {
765 assert_eq!(v[1 * 8 + 1], 200);
767 assert_eq!(v[1 * 8 + 4], 200);
768 assert_eq!(v[2 * 8 + 2], 0);
770 }
771 }
772
773 #[test]
774 fn test_f32_overlay_ignores_xor() {
775 let arr = NDArray::new(
776 vec![NDDimension::new(8), NDDimension::new(8)],
777 NDDataType::Float32,
778 );
779 let overlays = vec![OverlayDef {
780 shape: OverlayShape::Cross {
781 center_x: 4,
782 center_y: 4,
783 size: 2,
784 },
785 draw_mode: DrawMode::XOR, color: [100, 0, 0],
787 }];
788
789 let out = draw_overlays(&arr, &overlays);
790 if let NDDataBuffer::F32(ref v) = out.data {
791 assert_eq!(v[4 * 8 + 4], 100.0);
793 }
794 }
795}