1use brush_data::BrushToolData;
2use std::{cmp::Ordering, mem, thread};
3
4use crate::{
5 annotations_accessor_mut,
6 cfg::ExportPath,
7 events::{Events, KeyCode},
8 history::{History, Record},
9 instance_annotations_accessor, make_tool_transform,
10 meta_data::MetaData,
11 result::trace_ok_err,
12 tools::{
13 core::{check_recolorboxes, check_trigger_history_update, check_trigger_redraw},
14 instance_anno_shared::check_cocoimport,
15 },
16 tools_data::{
17 self,
18 annotations::{BrushAnnotations, InstanceAnnotations},
19 brush_data::{self, MAX_INTENSITY, MAX_THICKNESS, MIN_INTENSITY, MIN_THICKNESS},
20 coco_io::to_per_file_crowd,
21 vis_from_lfoption, ExportAsCoco, InstanceAnnotate, LabelInfo, Rot90ToolData,
22 },
23 tools_data_accessors, tools_data_accessors_objects,
24 util::Visibility,
25 world::World,
26 world_annotations_accessor, Annotation, BrushAnnotation, Line, ShapeI,
27};
28use rvimage_domain::{BrushLine, Canvas, PtF, TPtF};
29
30use super::{
31 core::{
32 change_annos, check_autopaste, check_erase_mode, check_instance_label_display_change,
33 deselect_all, instance_label_display_sort, label_change_key, map_held_key,
34 map_released_key, on_selection_keys, HeldKey, Mover, ReleasedKey,
35 },
36 instance_anno_shared::get_rot90_data,
37 Manipulate, BRUSH_NAME,
38};
39
40pub const ACTOR_NAME: &str = "Brush";
41const MISSING_ANNO_MSG: &str = "brush annotations have not yet been initialized";
42const MISSING_DATA_MSG: &str = "brush data not available";
43annotations_accessor_mut!(ACTOR_NAME, brush_mut, MISSING_ANNO_MSG, BrushAnnotations);
44world_annotations_accessor!(ACTOR_NAME, brush, MISSING_ANNO_MSG, BrushAnnotations);
45instance_annotations_accessor!(Canvas);
46tools_data_accessors!(
47 ACTOR_NAME,
48 MISSING_DATA_MSG,
49 brush_data,
50 BrushToolData,
51 brush,
52 brush_mut
53);
54tools_data_accessors_objects!(
55 ACTOR_NAME,
56 MISSING_DATA_MSG,
57 brush_data,
58 BrushToolData,
59 brush,
60 brush_mut
61);
62pub(super) fn change_annos_brush(world: &mut World, change: impl FnOnce(&mut BrushAnnotations)) {
63 change_annos::<_, DataAccessors, InstanceAnnoAccessors>(world, change);
64}
65
66fn import_coco(
67 meta_data: &MetaData,
68 coco_file: &ExportPath,
69 rot90_data: Option<&Rot90ToolData>,
70) -> Option<BrushToolData> {
71 trace_ok_err(tools_data::coco_io::read_coco(meta_data, coco_file, rot90_data).map(|(_, d)| d))
72}
73
74fn max_select_dist(shape: ShapeI) -> TPtF {
75 (TPtF::from(shape.w.pow(2) + shape.h.pow(2)).sqrt() / 100.0).max(50.0)
76}
77
78fn draw_erase_circle(mut world: World, mp: PtF) -> World {
79 let show_only_current = get_specific(&world).map(|d| d.label_info.show_only_current);
80 let options = get_options(&world).copied();
81 let idx_current = get_specific(&world).map(|d| d.label_info.cat_idx_current);
82 if let Some(options) = options {
83 let erase = |annos: &mut BrushAnnotations| {
84 let to_be_removed_line_idx = find_closest_canvas(annos, mp, |idx| {
85 annos.is_of_current_label(idx, idx_current, show_only_current)
86 });
87 if let Some((idx, _)) = to_be_removed_line_idx {
88 let canvas = annos.edit(idx);
89 trace_ok_err(canvas.draw_circle(mp, options.thickness, 0));
90 }
91 };
92 change_annos_brush(&mut world, erase);
93 set_visible(&mut world);
94 }
95 world
96}
97fn mouse_released(events: &Events, mut world: World, mut history: History) -> (World, History) {
98 if events.held_ctrl() {
99 let shape_orig = world.shape_orig();
100 let show_only_current = get_specific(&world).map(|d| d.label_info.show_only_current);
101 let idx_current = get_specific(&world).map(|d| d.label_info.cat_idx_current);
102 if let (Some(mp), Some(annos)) = (events.mouse_pos_on_orig, get_annos_mut(&mut world)) {
103 let to_be_selected_line_idx = find_closest_canvas(annos, mp, |idx| {
104 annos.is_of_current_label(idx, idx_current, show_only_current)
105 });
106 if let Some((idx, dist)) = to_be_selected_line_idx {
107 if dist < max_select_dist(shape_orig) {
108 if annos.selected_mask()[idx] {
109 annos.deselect(idx);
110 } else {
111 annos.select(idx);
112 }
113 } else {
114 world =
115 deselect_all::<_, DataAccessors, InstanceAnnoAccessors>(world, BRUSH_NAME);
116 }
117 }
118 }
119 set_visible(&mut world);
120 } else if !(events.held_alt() || events.held_shift()) {
121 let erase = get_options(&world).map(|o| o.core.erase);
124 let cat_idx = get_specific(&world).map(|o| o.label_info.cat_idx_current);
125 if erase != Some(true) {
126 let shape_orig = world.shape_orig();
127 let line = get_specific_mut(&mut world).and_then(|d| mem::take(&mut d.tmp_line));
128 let line = if let Some((line, _)) = line {
129 Some(line)
130 } else if let (Some(mp), Some(options)) =
131 (events.mouse_pos_on_orig, get_options(&world))
132 {
133 Some(BrushLine {
134 line: Line::from(mp),
135 intensity: options.intensity,
136 thickness: options.thickness,
137 })
138 } else {
139 None
140 };
141 let ild = get_instance_label_display(&world);
142
143 let change_annos = |annos: &mut BrushAnnotations| {
144 if let (Some(line), Some(cat_idx)) = (line, cat_idx) {
145 let canvas = Canvas::new(&line, shape_orig, None);
146 if let Ok(canvas) = canvas {
147 annos.add_elt(canvas, cat_idx, ild);
148 }
149 }
150 };
151 change_annos_brush(&mut world, change_annos);
152 set_visible(&mut world);
153 } else if let Some(mp) = events.mouse_pos_on_orig {
154 world = draw_erase_circle(world, mp);
155 }
156 history.push(Record::new(world.clone(), ACTOR_NAME));
157 }
158 (world, history)
159}
160fn mouse_pressed_left(events: &Events, mut world: World) -> World {
161 if !(events.held_alt() || events.held_ctrl() || events.held_shift()) {
162 world = deselect_all::<_, DataAccessors, InstanceAnnoAccessors>(world, BRUSH_NAME);
163 }
164 if !events.held_ctrl() {
165 let options = get_options(&world).copied();
166 let idx_current = get_specific(&world).map(|d| d.label_info.cat_idx_current);
167 if let (Some(mp), Some(options)) = (events.mouse_pos_on_orig, options) {
168 let erase = options.core.erase;
169 if !erase {
170 if let (Some(d), Some(cat_idx)) = (get_specific_mut(&mut world), idx_current) {
171 let line = Line::from(mp);
172 d.tmp_line = Some((
173 BrushLine {
174 line,
175 intensity: options.intensity,
176 thickness: options.thickness,
177 },
178 cat_idx,
179 ));
180 }
181 }
182 }
183 set_visible(&mut world);
184 }
185 world
186}
187fn key_released(events: &Events, mut world: World, mut history: History) -> (World, History) {
188 let released_key = map_released_key(events);
189 (world, history) = on_selection_keys::<_, DataAccessors, InstanceAnnoAccessors>(
190 world,
191 history,
192 released_key,
193 events.held_ctrl(),
194 BRUSH_NAME,
195 );
196 let mut trigger_redraw = false;
197 if let Some(label_info) = get_specific_mut(&mut world).map(|s| &mut s.label_info) {
198 (*label_info, trigger_redraw) = label_change_key(released_key, mem::take(label_info));
199 }
200 if trigger_redraw {
201 let visible = get_options(&world).map(|o| o.core.visible) == Some(true);
202 let vis = vis_from_lfoption(get_label_info(&world), visible);
203 world.request_redraw_annotations(BRUSH_NAME, vis);
204 }
205 match released_key {
206 ReleasedKey::H if events.held_ctrl() => {
207 if let Some(options_mut) = get_options_mut(&mut world) {
209 options_mut.core.visible = !options_mut.core.visible;
210 }
211 let vis = get_visible(&world);
212 world.request_redraw_annotations(BRUSH_NAME, vis);
213 }
214 _ => (),
215 }
216 world = check_instance_label_display_change::<_, DataAccessors, InstanceAnnoAccessors>(
217 world,
218 released_key,
219 ACTOR_NAME,
220 );
221 world = check_erase_mode::<DataAccessors>(released_key, set_visible, world);
222 (world, history)
223}
224fn find_closest_canvas(
225 annos: &BrushAnnotations,
226 p: PtF,
227 predicate: impl Fn(usize) -> bool,
228) -> Option<(usize, f64)> {
229 annos
230 .elts()
231 .iter()
232 .enumerate()
233 .map(|(i, cvs)| {
234 (
235 i,
236 cvs.dist_to_boundary(p) * if cvs.contains(p) { 0.0 } else { 1.0 },
237 )
238 })
239 .filter(|(i, _)| predicate(*i))
240 .min_by(|(_, x), (_, y)| match x.partial_cmp(y) {
241 Some(o) => o,
242 None => Ordering::Greater,
243 })
244}
245
246fn check_selected_intensity_thickness(mut world: World) -> World {
247 let options = get_options(&world).copied();
248 let annos = get_annos_mut(&mut world);
249 let mut any_selected = false;
250 if let (Some(annos), Some(options)) = (annos, options) {
251 if options.is_selection_change_needed {
252 for brushline in annos.selected_elts_iter_mut() {
253 brushline.intensity = options.intensity;
254 any_selected = true;
255 }
256 }
257 }
258 let options_mut = get_options_mut(&mut world);
259 if let Some(options_mut) = options_mut {
260 options_mut.is_selection_change_needed = false;
261 if any_selected {
262 options_mut.core.is_redraw_annos_triggered = true;
263 }
264 }
265 world
266}
267
268fn check_export(mut world: World) -> World {
269 let options = get_options(&world);
270 let specifics = get_specific(&world);
271
272 if options.map(|o| o.core.import_export_trigger.export_triggered()) == Some(true) {
273 let rot90_data = get_rot90_data(&world).cloned();
274 if let Some(data) = specifics {
275 let meta_data = world.data.meta_data.clone();
276 let mut data = data.clone();
277 let per_file_crowd = options.map(|o| o.per_file_crowd) == Some(true);
278 let f_export = move || {
279 let start = std::time::Instant::now();
280 if per_file_crowd {
281 to_per_file_crowd(&mut data.annotations_map);
282 }
283 let coco_file_conn = data.cocofile_conn();
284 match tools_data::write_coco(&meta_data, data, rot90_data.as_ref(), &coco_file_conn)
285 {
286 Ok((p, _)) => tracing::info!("export to {p:?} successfully triggered"),
287 Err(e) => tracing::error!("trigger export failed due to {e:?}"),
288 };
289 tracing::info!("export took {} seconds", start.elapsed().as_secs_f32());
290 };
291 thread::spawn(f_export);
292 }
293 if let Some(options_mut) = get_options_mut(&mut world) {
294 options_mut.core.import_export_trigger.untrigger_export();
295 }
296 }
297 world
298}
299
300pub(super) fn on_mouse_held_right(
301 mouse_pos: Option<PtF>,
302 mover: &mut Mover,
303 mut world: World,
304 history: History,
305) -> (World, History) {
306 if get_options(&world).map(|o| o.core.erase) != Some(true) {
307 let orig_shape = world.data.shape();
308 let move_boxes = |mpo_from, mpo_to| {
309 let annos = get_annos_mut(&mut world);
310 if let Some(annos) = annos {
311 let (mut elts, cat_idxs, selected_mask) = mem::take(annos).separate_data();
312 for (i, anno) in elts.iter_mut().enumerate() {
313 if selected_mask[i] {
314 anno.follow_movement(mpo_from, mpo_to, orig_shape);
315 }
316 }
317 *annos = InstanceAnnotations::new(elts, cat_idxs, selected_mask).unwrap();
318 }
319 Some(())
320 };
321 mover.move_mouse_held(move_boxes, mouse_pos);
322 let vis = get_visible(&world);
323 world.request_redraw_annotations(ACTOR_NAME, vis);
324 }
325 (world, history)
326}
327#[derive(Clone, Debug)]
328pub struct Brush {
329 mover: Mover,
330}
331
332impl Brush {
333 fn mouse_pressed(
334 &mut self,
335 events: &Events,
336 mut world: World,
337 history: History,
338 ) -> (World, History) {
339 if events.pressed(KeyCode::MouseRight) {
340 self.mover.move_mouse_pressed(events.mouse_pos_on_orig);
341 } else {
342 world = mouse_pressed_left(events, world);
343 }
344 (world, history)
345 }
346 fn mouse_held(
347 &mut self,
348 events: &Events,
349 mut world: World,
350 history: History,
351 ) -> (World, History) {
352 if events.held(KeyCode::MouseRight) {
353 on_mouse_held_right(events.mouse_pos_on_orig, &mut self.mover, world, history)
354 } else {
355 if !events.held_ctrl() {
356 let options = get_options(&world).copied();
357 if let (Some(mp), Some(options)) = (events.mouse_pos_on_orig, options) {
358 if options.core.erase {
359 world = draw_erase_circle(world, mp);
360 } else {
361 let line = if let Some((line, _)) =
362 get_specific_mut(&mut world).and_then(|d| d.tmp_line.as_mut())
363 {
364 let last_point = line.line.last_point();
365 let dist = if let Some(last_point) = last_point {
366 last_point.dist_square(&mp)
367 } else {
368 100.0
369 };
370 if dist >= 3.0 {
371 line.line.push(mp);
372 }
373 Some(line.clone())
374 } else {
375 None
376 };
377 if let (Some(line), Some(color)) = (
378 line,
379 get_specific(&world)
380 .map(|d| d.label_info.colors()[d.label_info.cat_idx_current]),
381 ) {
382 let orig_shape = world.shape_orig();
383 let canvas_with_new_buffer = || {
384 let lower_buffer_bound = 100;
385 let extension_factor = if line.line.points.len() < 10 {
386 4.0
387 } else if line.line.points.len() < 50 {
388 3.0
389 } else {
390 2.0
391 };
392 Canvas::from_line_extended(
393 &line,
394 orig_shape,
395 extension_factor,
396 lower_buffer_bound,
397 )
398 };
399 let canvas = if let Some(buffer) =
400 mem::take(&mut world.update_view.tmp_anno_buffer)
401 {
402 match buffer {
403 Annotation::Brush(brush_anno) => {
404 tracing::debug!("found buffer for tmp anno");
405 Canvas::new(&line, orig_shape, Some(brush_anno.canvas.mask))
406 }
407 _ => canvas_with_new_buffer(),
408 }
409 } else {
410 canvas_with_new_buffer()
411 };
412
413 let canvas = trace_ok_err(canvas);
414 if let Some(canvas) = canvas {
415 world.request_redraw_tmp_anno(Annotation::Brush(BrushAnnotation {
416 canvas,
417 color,
418 label: None,
419 is_selected: None,
420 fill_alpha: options.fill_alpha,
421 instance_display_label: options.core.instance_label_display,
422 }));
423 }
424 }
425 }
426 }
427 }
428
429 (world, history)
430 }
431 }
432
433 fn mouse_released(
434 &mut self,
435 events: &Events,
436 world: World,
437 history: History,
438 ) -> (World, History) {
439 mouse_released(events, world, history)
440 }
441
442 #[allow(clippy::unused_self)]
443 fn key_released(
444 &mut self,
445 events: &Events,
446 world: World,
447 history: History,
448 ) -> (World, History) {
449 key_released(events, world, history)
450 }
451 fn key_held(
452 &mut self,
453 events: &Events,
454 mut world: World,
455 history: History,
456 ) -> (World, History) {
457 const INTENSITY_STEP: f64 = MAX_INTENSITY / 20.0;
458 const THICKNESS_STEP: f64 = MAX_THICKNESS / 20.0;
459 let held_key = map_held_key(events);
460 let snap_to_step = |x: TPtF, step: TPtF| {
461 if x < 2.0 * step {
462 (x.div_euclid(step)) * step
463 } else {
464 x
465 }
466 };
467 match held_key {
468 HeldKey::I if events.held_alt() => {
469 if let Some(o) = get_options_mut(&mut world) {
470 o.intensity = MIN_INTENSITY
471 .max(snap_to_step(o.intensity - INTENSITY_STEP, INTENSITY_STEP));
472 o.is_selection_change_needed = true;
473 }
474 }
475 HeldKey::I => {
476 if let Some(o) = get_options_mut(&mut world) {
477 o.intensity = MAX_INTENSITY
478 .min(snap_to_step(o.intensity + INTENSITY_STEP, INTENSITY_STEP));
479 o.is_selection_change_needed = true;
480 }
481 }
482 HeldKey::T if events.held_alt() => {
483 if let Some(o) = get_options_mut(&mut world) {
484 o.thickness = MIN_THICKNESS
485 .max(snap_to_step(o.thickness - THICKNESS_STEP, THICKNESS_STEP));
486 o.is_selection_change_needed = true;
487 }
488 }
489 HeldKey::T => {
490 if let Some(o) = get_options_mut(&mut world) {
491 o.thickness = MAX_THICKNESS
492 .min(snap_to_step(o.thickness + THICKNESS_STEP, THICKNESS_STEP));
493 o.is_selection_change_needed = true;
494 }
495 }
496 HeldKey::None => (),
497 }
498 (world, history)
499 }
500}
501
502impl Manipulate for Brush {
503 fn new() -> Self {
504 Self {
505 mover: Mover::new(),
506 }
507 }
508
509 fn on_filechange(&mut self, mut world: World, mut history: History) -> (World, History) {
510 let brush_data = get_specific_mut(&mut world);
511 if let Some(brush_data) = brush_data {
512 for (_, (anno, _)) in brush_data.anno_iter_mut() {
513 anno.deselect_all();
514 }
515 let ild = get_instance_label_display(&world);
516 world = instance_label_display_sort::<_, DataAccessors, InstanceAnnoAccessors>(
517 world, ild, ACTOR_NAME,
518 );
519 }
520 (world, history) =
521 check_autopaste::<_, DataAccessors, InstanceAnnoAccessors>(world, history, ACTOR_NAME);
522 set_visible(&mut world);
523 (world, history)
524 }
525 fn on_activate(&mut self, mut world: World) -> World {
526 if let Some(data) = trace_ok_err(get_data_mut(&mut world)) {
527 data.menu_active = true;
528 }
529 set_visible(&mut world);
530 world
531 }
532 fn on_deactivate(&mut self, mut world: World) -> World {
533 if let Some(data) = trace_ok_err(get_data_mut(&mut world)) {
534 data.menu_active = false;
535 }
536 world.request_redraw_annotations(BRUSH_NAME, Visibility::None);
537 world
538 }
539 fn on_always_active_zoom(&mut self, mut world: World, history: History) -> (World, History) {
540 let visible = get_options(&world).map(|o| o.core.visible) == Some(true);
541 let vis = vis_from_lfoption(get_label_info(&world), visible);
542 world.request_redraw_annotations(BRUSH_NAME, vis);
543 (world, history)
544 }
545 fn events_tf(
546 &mut self,
547 mut world: World,
548 mut history: History,
549 events: &Events,
550 ) -> (World, History) {
551 world = check_trigger_redraw::<DataAccessors>(world, BRUSH_NAME);
552 (world, history) =
553 check_trigger_history_update::<DataAccessors>(world, history, BRUSH_NAME);
554 let imported;
555 (world, imported) = check_cocoimport::<_, _, DataAccessors>(
556 world,
557 get_specific,
558 get_specific_mut,
559 import_coco,
560 );
561 if imported {
562 set_visible(&mut world);
563 }
564 world = check_recolorboxes::<DataAccessors>(world, BRUSH_NAME);
565 world = check_selected_intensity_thickness(world);
566 world = check_export(world);
567 make_tool_transform!(
568 self,
569 world,
570 history,
571 events,
572 [
573 (pressed, KeyCode::MouseLeft, mouse_pressed),
574 (pressed, KeyCode::MouseRight, mouse_pressed),
575 (held, KeyCode::MouseLeft, mouse_held),
576 (held, KeyCode::MouseRight, mouse_held),
577 (released, KeyCode::MouseLeft, mouse_released),
578 (released, KeyCode::Back, key_released),
579 (released, KeyCode::Delete, key_released),
580 (released, KeyCode::A, key_released),
581 (released, KeyCode::C, key_released),
582 (released, KeyCode::D, key_released),
583 (released, KeyCode::E, key_released),
584 (released, KeyCode::H, key_released),
585 (held, KeyCode::I, key_held),
586 (released, KeyCode::L, key_released),
587 (held, KeyCode::T, key_held),
588 (released, KeyCode::V, key_released),
589 (released, KeyCode::Key1, key_released),
590 (released, KeyCode::Key2, key_released),
591 (released, KeyCode::Key3, key_released),
592 (released, KeyCode::Key4, key_released),
593 (released, KeyCode::Key5, key_released),
594 (released, KeyCode::Key6, key_released),
595 (released, KeyCode::Key7, key_released),
596 (released, KeyCode::Key8, key_released),
597 (released, KeyCode::Key9, key_released)
598 ]
599 )
600 }
601}
602
603#[cfg(test)]
604use {
605 crate::{tracing_setup::init_tracing_for_tests, types::ViewImage},
606 image::DynamicImage,
607};
608
609#[cfg(test)]
610pub fn test_data() -> (Option<PtF>, World, History) {
611 use std::path::Path;
612
613 use crate::ToolsDataMap;
614 let im_test = DynamicImage::ImageRgb8(ViewImage::new(64, 64));
615 let mut world = World::from_real_im(
616 im_test,
617 ToolsDataMap::new(),
618 None,
619 Some("superimage.png".to_string()),
620 Path::new("superimage.png"),
621 Some(0),
622 );
623 world.data.meta_data.flags.is_loading_screen_active = Some(false);
624 get_specific_mut(&mut world)
625 .unwrap()
626 .label_info
627 .push("label".to_string(), None, None)
628 .unwrap();
629 let history = History::default();
630 let mouse_pos = Some((32.0, 32.0).into());
631 (mouse_pos, world, history)
632}
633
634#[test]
635fn test_mouse_released() {
636 init_tracing_for_tests();
637 let (mp, mut world, history) = test_data();
638 let options = get_options_mut(&mut world).unwrap();
639 options.thickness = 1.0;
640 let mut events = Events::default();
641 events.mouse_pos_on_orig = mp;
642 let (world, history) = mouse_released(&events, world, history);
643 let annos = get_annos(&world).unwrap();
644 assert_eq!(annos.len(), 1);
645 assert_eq!(annos.elts()[0].bb.x, 32);
646 assert_eq!(annos.elts()[0].bb.y, 32);
647 events.mouse_pos_on_orig = Some((40, 40).into());
648 let world = mouse_pressed_left(&events, world);
649 let (world, history) = mouse_released(&events, world, history);
650 let annos = get_annos(&world).unwrap();
651 assert_eq!(annos.len(), 2);
652 assert_eq!(annos.elts()[0].bb.x, 32);
653 assert_eq!(annos.elts()[0].bb.y, 32);
654 assert_eq!(annos.elts()[1].bb.x, 40);
655 assert_eq!(annos.elts()[1].bb.y, 40);
656 events.mouse_pos_on_orig = Some((10, 10).into());
657 let (world, _) = mouse_released(&events, world, history);
658 let annos = get_annos(&world).unwrap();
659 assert_eq!(annos.len(), 3);
660 assert_eq!(annos.elts()[0].bb.x, 32);
661 assert_eq!(annos.elts()[0].bb.y, 32);
662 assert_eq!(annos.elts()[1].bb.x, 40);
663 assert_eq!(annos.elts()[1].bb.y, 40);
664 assert_eq!(annos.elts()[2].bb.x, 10);
665 assert_eq!(annos.elts()[2].bb.y, 10);
666}